From 83a7f508fd57f874a397039dadfb7f2f691c4907 Mon Sep 17 00:00:00 2001 From: Alexander Sklar Date: Sun, 9 Apr 2023 04:30:51 -0700 Subject: [PATCH 01/21] Add property, event, and INPC helpers --- include/wil/property.h | 234 +++++++++++++++++++++++++++++++++ tests/CMakeLists.txt | 19 +-- tests/PropertyTests.cpp | 157 ++++++++++++++++++++++ tests/cpplatest/CMakeLists.txt | 5 + tests/cpplatest/app.manifest | 23 ++++ 5 files changed, 429 insertions(+), 9 deletions(-) create mode 100644 include/wil/property.h create mode 100644 tests/PropertyTests.cpp create mode 100644 tests/cpplatest/app.manifest diff --git a/include/wil/property.h b/include/wil/property.h new file mode 100644 index 00000000..9665cba3 --- /dev/null +++ b/include/wil/property.h @@ -0,0 +1,234 @@ +//********************************************************* +// +// Copyright (c) Microsoft. All rights reserved. +// This code is licensed under the MIT License. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +// PARTICULAR PURPOSE AND NONINFRINGEMENT. +// +//********************************************************* + +#pragma once +#ifndef __WIL_PROPERTY_INCLUDED +#define __WIL_PROPERTY_INCLUDED + +namespace wil +{ + +template +struct read_only_property +{ + read_only_property(T value) : m_value(value) { } + + operator T() const noexcept { return m_value; } + const T& operator()() const noexcept { return m_value; } + + read_only_property(const read_only_property& other) noexcept : m_value(other.m_value) { } + read_only_property(read_only_property&& other) noexcept : m_value(std::move(other.m_value)) { } + + template + read_only_property(TArgs&&... args) : m_value(std::forward(args)...) { } + + protected: + T m_value; +}; + +template +struct read_write_property : read_only_property +{ + read_write_property(T value) : read_only_property(value) { } + + template + read_write_property(TArgs&&... args) : read_only_property(std::forward(args)...) { } + + using read_only_property::operator T; + using read_only_property::operator(); + read_write_property& operator()(const T& value) noexcept + { + this->m_value = value; + return *this; + } + + read_write_property& operator=(T value) noexcept + { + this->m_value = value; + return *this; + } + + template>> + read_write_property& operator=(const read_only_property& other) noexcept + { + this->m_value = other.m_value; + return *this; + } + + template>> + read_write_property& operator=(read_write_property&& other) noexcept + { + this->m_value = std::move(other.m_value); + return *this; + } +}; + +#pragma region WinRT / XAML helpers +#ifdef WINRT_Windows_Foundation_H + +namespace details { + template + struct event_base { + winrt::event_token operator()(T const& handler) { + return m_handler.add(handler); + } + auto operator()(const winrt::event_token& token) noexcept { return m_handler.remove(token); } + + template + auto invoke(TArgs&&... args) { + return m_handler(args...); + } + private: + winrt::event m_handler; + }; +} + +/** + * @brief A default event handler that maps to [Windows.Foundation.EventHandler](https://docs.microsoft.com/uwp/api/windows.foundation.eventhandler-1). + * @tparam T The event data type. +*/ +template +struct simple_event : details::event_base> {}; + +/** + * @brief A default event handler that maps to [Windows.Foundation.TypedEventHandler](https://docs.microsoft.com/uwp/api/windows.foundation.typedeventhandler-2). + * @tparam T The event data type. + * @details Usage example: + * @code + * // In IDL, this corresponds to: + * // event Windows.Foundation.TypedEventHandler OkClicked; + * wil::typed_event OkClicked; + * @endcode +*/ +template +struct typed_event : details::event_base> {}; + +#endif +#pragma endregion + +#pragma region INotifyPropertyChanged helpers +#ifdef WINRT_Windows_UI_Xaml_Data_H + +/** + * @brief Helper base class to inherit from to have a simple implementation of [INotifyPropertyChanged](https://docs.microsoft.com/uwp/api/windows.ui.xaml.data.inotifypropertychanged). + * @tparam T CRTP type + * @details When you declare your class, make this class a base class and pass your class as a template parameter: + * @code + * struct MyPage : MyPageT, wil::notify_property_changed_base { + * wil::property_with_notify MyInt; + * + * MyPage() : INIT_PROPERTY(MyInt, 42) { } + * }; + * @endcode +*/ +template> +> +struct notify_property_changed_base { +public: + using Type = T; + auto PropertyChanged(Xaml_Data_PropertyChangedEventHandler const& value) { + return m_propertyChanged.add(value); + } + void PropertyChanged(winrt::event_token const& token) { + m_propertyChanged.remove(token); + } + Type& self() { + return *static_cast(this); + } + + /** + * @brief Raises a property change notification event + * @param name The name of the property + * @return + * @details Usage example\n + * XAML file + * @code{.xml} + * + * + * @endcode + * C++ + * @code + * void MyPage::DoSomething() { + * // modify MyInt + * // MyInt = ... + * + * // now send a notification to update the bound UI elements + * RaisePropertyChanged(L"MyInt"); + * } + * @endcode + */ + auto RaisePropertyChanged(std::wstring_view name) { + return m_propertyChanged(self(), Xaml_Data_PropertyChangedEventHandler{ name }); + } +protected: + winrt::event m_propertyChanged; + + template + friend struct readonly_property; +}; + +/** + * @brief Implements a property type with notifications + * @tparam T the property type + * @details Use the #INIT_NOTIFY_PROPERTY macro to initialize this property in your class constructor. This will set up the right property name, and bind it to the `SimpleNotifyPropertyChanged` implementation. +*/ +template +struct property_with_notify : read_write_property { + using Type = T; + + using read_write_property::operator(); + + void operator()(const T& value) { + if (value != operator()()) { + read_write_property::operator()(value); + if (m_npc) { + (*m_npc)(m_sender, Xaml_Data_PropertyChangedEventArgs{ m_name }); + } + } + } + template + property_with_notify( + winrt::event* npc, + winrt::Windows::Foundation::IInspectable sender, + std::wstring_view name, + TArgs&&... args) : + read_write_property(std::forward(args)...) { + m_name = name; + m_npc = npc; + m_sender = sender; + } + + property_with_notify(const property_with_notify&) = default; + property_with_notify(property_with_notify&&) = default; +private: + std::wstring m_name; + winrt::event* m_npc; + winrt::Windows::Foundation::IInspectable m_sender; +}; + +/** +* @def INIT_NOTIFY_PROPERTY +* @brief use this to initialize a wil::property_with_notify in your class constructor. +*/ +#define INIT_NOTIFY_PROPERTY(NAME, VALUE) \ + NAME(&m_propertyChanged, *this, std::wstring_view{ L#NAME }, VALUE) + + +#endif + +#pragma endregion // INotifyPropertyChanged helpers + +} // namespace wil +#endif // __WIL_PROPERTY_INCLUDED \ No newline at end of file diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 5020a6c3..32c94b85 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -23,7 +23,7 @@ endif() set(CPPWINRT ${CMAKE_BINARY_DIR}/packages/Microsoft.Windows.CppWinRT.${CPPWINRT_VERSION}/bin/cppwinrt.exe) execute_process(COMMAND - ${CPPWINRT} -input sdk -output include + ${CPPWINRT} -input sdk+ -output include WORKING_DIRECTORY ${CMAKE_BINARY_DIR} RESULT_VARIABLE ret) if (NOT ret EQUAL 0) @@ -41,10 +41,10 @@ endif() # For some unknown reason, 'RelWithDebInfo' compiles with '/Ob1' as opposed to '/Ob2' which prevents inlining of # functions not marked 'inline'. The reason we prefer 'RelWithDebInfo' over 'Release' is to get debug info, so manually # revert to the desired (and default) inlining behavior as that exercises more interesting code paths -if (${CMAKE_BUILD_TYPE} STREQUAL "RelWithDebInfo") - # TODO: This is currently blocked by an apparent Clang bug: https://github.com/llvm/llvm-project/issues/59690 - # replace_cxx_flag("/Ob1" "/Ob2") -endif() +# if (${CMAKE_BUILD_TYPE} STREQUAL "RelWithDebInfo") +# # TODO: This is currently blocked by an apparent Clang bug: https://github.com/llvm/llvm-project/issues/59690 +# # replace_cxx_flag("/Ob1" "/Ob2") +# endif() set(COMMON_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp @@ -52,6 +52,7 @@ set(COMMON_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/ComTests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/FileSystemTests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/NTResultTests.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/PropertyTests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ResourceTests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ResultTests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Rpc.cpp @@ -73,12 +74,12 @@ add_subdirectory(win7) set(DEBUG_BUILD FALSE) set(HAS_DEBUG_INFO FALSE) -if (${CMAKE_BUILD_TYPE} STREQUAL "Debug") +#if (${CMAKE_BUILD_TYPE} STREQUAL "Debug") set(DEBUG_BUILD TRUE) set(HAS_DEBUG_INFO TRUE) -elseif(${CMAKE_BUILD_TYPE} STREQUAL "RelWithDebInfo") - set(HAS_DEBUG_INFO TRUE) -endif() +#elseif(${CMAKE_BUILD_TYPE} STREQUAL "RelWithDebInfo") +# set(HAS_DEBUG_INFO TRUE) +#endif() set(ASAN_AVAILABLE FALSE) set(UBSAN_AVAILABLE FALSE) diff --git a/tests/PropertyTests.cpp b/tests/PropertyTests.cpp new file mode 100644 index 00000000..926fcbae --- /dev/null +++ b/tests/PropertyTests.cpp @@ -0,0 +1,157 @@ +#include "common.h" +// check if at least C++17 +#if _MSVC_LANG >= 201703L +#include +#include +#endif + +#include + +TEST_CASE("PropertyTests::ReadOnly", "[property]") +{ + int value = 42; + wil::read_only_property prop(value); + REQUIRE(prop == value); + REQUIRE(prop() == value); + REQUIRE(prop == prop()); + REQUIRE(prop == prop); + + wil::read_only_property prop2 = prop; + REQUIRE(prop2 == value); + REQUIRE(prop2() == value); + REQUIRE(prop2 == prop()); + REQUIRE(prop2 == prop); +} + +TEST_CASE("PropertyTests::ReadWrite", "[property]") +{ + int value = 42; + wil::read_write_property prop(value); + REQUIRE(prop == value); + REQUIRE(prop() == value); + REQUIRE(prop == prop()); + REQUIRE(prop == prop); + + wil::read_write_property prop2 = prop; + REQUIRE(prop2 == value); + REQUIRE(prop2() == value); + REQUIRE(prop2 == prop()); + REQUIRE(prop2 == prop); + + int value2 = 43; + prop2 = value2; + REQUIRE(prop2 == value2); + REQUIRE(prop2() == value2); + REQUIRE(prop2 == prop2()); + REQUIRE(prop2 == prop2); +} + +TEST_CASE("PropertyTests::ReadWriteFromReadOnly", "[property]") +{ + int value = 42; + wil::read_only_property prop(value); + REQUIRE(prop == value); + REQUIRE(prop() == value); + REQUIRE(prop == prop()); + REQUIRE(prop == prop); + + wil::read_write_property prop2 = prop; + REQUIRE(prop2 == value); + REQUIRE(prop2() == value); + REQUIRE(prop2 == prop()); + REQUIRE(prop2 == prop); + + int value2 = 43; + prop2 = value2; + REQUIRE(prop2 == value2); + REQUIRE(prop2() == value2); + REQUIRE(prop2 == prop2()); + REQUIRE(prop2 == prop2); +} + +TEST_CASE("PropertyTests::InStruct", "[property]") +{ + struct TestStruct + { + wil::read_only_property Prop1{ 42 }; + wil::read_write_property Prop2{ 1 }; + wil::read_only_property Prop3{ 44 }; + }; + + TestStruct test; + static_assert(!std::is_assignable_v, int>, "cannot assign to a readonly property"); + + test.Prop2 = 43; + static_assert(!std::is_assignable_v, "cannot assign to a readonly property"); + + REQUIRE(test.Prop1 == 42); + REQUIRE(test.Prop2 == 43); + REQUIRE(test.Prop3 == 44); + + test.Prop2 = 45; + REQUIRE(test.Prop2 == 45); + + REQUIRE(test.Prop1() == 42); + test.Prop2(99); + REQUIRE(test.Prop2() == 99); + test.Prop2(22)(33); + REQUIRE(test.Prop2() == 33); +} + +#ifdef WINRT_Windows_Foundation_H +TEST_CASE("PropertyTests::Events", "[property]") +{ + struct Test + { + wil::simple_event MyEvent; + + wil::typed_event MyTypedEvent; + } test; + + auto token = test.MyEvent([](winrt::Windows::Foundation::IInspectable, int args) { REQUIRE(args == 42); }); + test.MyEvent.invoke(nullptr, 42); + test.MyEvent(token); + + auto token2 = test.MyTypedEvent([](winrt::Windows::Foundation::IInspectable, int args) { REQUIRE(args == 42); }); + test.MyTypedEvent.invoke(nullptr, 42); + test.MyTypedEvent(token2); +} +#endif // WINRT_Windows_Foundation_H + +// if using MSVC since we need the embedded manifest bits to use XAML islands +#if _MSVC_LANG >= 201703L && defined(WINRT_Windows_UI_Xaml_Data_H) + +#include + +TEST_CASE("PropertyTests::NotifyPropertyChanged", "[property]") +{ + winrt::init_apartment(winrt::apartment_type::single_threaded); + + // We need to initialize the XAML core in order to instantiate a PropertyChangedEventArgs [sigh]. + // This is a bit of a hack, but it works. + winrt::Windows::UI::Xaml::Hosting::DesktopWindowXamlSource dwxs; + + + + struct Test : winrt::implements, wil::notify_property_changed_base + { + using wil::notify_property_changed_base::PropertyChanged; + wil::property_with_notify MyProperty; + Test() : INIT_NOTIFY_PROPERTY(MyProperty, 42) {} + }; + auto test = winrt::make(); + auto testImpl = winrt::get_self(test); + + auto token = test.PropertyChanged([&](winrt::Windows::Foundation::IInspectable, winrt::Windows::UI::Xaml::Data::PropertyChangedEventArgs args) + { + REQUIRE(args.PropertyName() == L"MyProperty"); + REQUIRE(testImpl->MyProperty() == 43); + }); + + testImpl->MyProperty(43); + + + winrt::uninit_apartment(); + +} +#endif // msvc \ No newline at end of file diff --git a/tests/cpplatest/CMakeLists.txt b/tests/cpplatest/CMakeLists.txt index 7ae64d54..a2f1504f 100644 --- a/tests/cpplatest/CMakeLists.txt +++ b/tests/cpplatest/CMakeLists.txt @@ -14,11 +14,16 @@ else() ${CMAKE_CURRENT_SOURCE_DIR}/../ComApartmentVariableTests.cpp) endif() +if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC") + target_link_options(witest.cpplatest PRIVATE /manifestinput:${CMAKE_CURRENT_SOURCE_DIR}/app.manifest) +endif() + target_sources(witest.cpplatest PRIVATE ${COMMON_SOURCES} ${COROUTINE_SOURCES} ${CMAKE_CURRENT_SOURCE_DIR}/../CppWinRTTests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../CppWinRT20Tests.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../PropertyTests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../StlTests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../TokenHelpersTests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../UniqueWinRTEventTokenTests.cpp diff --git a/tests/cpplatest/app.manifest b/tests/cpplatest/app.manifest new file mode 100644 index 00000000..45127741 --- /dev/null +++ b/tests/cpplatest/app.manifest @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + From 81e6a3a8c6b4acaa148e5a5952ffba999d3efbc1 Mon Sep 17 00:00:00 2001 From: Alexander Sklar Date: Sun, 9 Apr 2023 04:41:15 -0700 Subject: [PATCH 02/21] . --- include/wil/property.h | 8 -------- 1 file changed, 8 deletions(-) diff --git a/include/wil/property.h b/include/wil/property.h index 9665cba3..cb246706 100644 --- a/include/wil/property.h +++ b/include/wil/property.h @@ -151,11 +151,6 @@ struct notify_property_changed_base { * @param name The name of the property * @return * @details Usage example\n - * XAML file - * @code{.xml} - * - * - * @endcode * C++ * @code * void MyPage::DoSomething() { @@ -172,9 +167,6 @@ struct notify_property_changed_base { } protected: winrt::event m_propertyChanged; - - template - friend struct readonly_property; }; /** From 3685e15deb41ebf5e5b891f11d6206ae61ffe73e Mon Sep 17 00:00:00 2001 From: Alexander Sklar Date: Sun, 9 Apr 2023 04:43:01 -0700 Subject: [PATCH 03/21] . --- include/wil/property.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/wil/property.h b/include/wil/property.h index cb246706..2f539226 100644 --- a/include/wil/property.h +++ b/include/wil/property.h @@ -172,7 +172,7 @@ struct notify_property_changed_base { /** * @brief Implements a property type with notifications * @tparam T the property type - * @details Use the #INIT_NOTIFY_PROPERTY macro to initialize this property in your class constructor. This will set up the right property name, and bind it to the `SimpleNotifyPropertyChanged` implementation. + * @details Use the #INIT_NOTIFY_PROPERTY macro to initialize this property in your class constructor. This will set up the right property name, and bind it to the `notify_property_changed_base` implementation. */ template Date: Sun, 9 Apr 2023 15:08:51 -0700 Subject: [PATCH 04/21] rename header/tests, format. --- include/wil/cppwinrt_authoring.h | 222 +++++++++++++++++ include/wil/property.h | 226 ------------------ tests/CMakeLists.txt | 1 - ...tyTests.cpp => CppWinRTAuthoringTests.cpp} | 34 ++- tests/cpplatest/CMakeLists.txt | 2 +- 5 files changed, 239 insertions(+), 246 deletions(-) create mode 100644 include/wil/cppwinrt_authoring.h delete mode 100644 include/wil/property.h rename tests/{PropertyTests.cpp => CppWinRTAuthoringTests.cpp} (79%) diff --git a/include/wil/cppwinrt_authoring.h b/include/wil/cppwinrt_authoring.h new file mode 100644 index 00000000..16cb2421 --- /dev/null +++ b/include/wil/cppwinrt_authoring.h @@ -0,0 +1,222 @@ +//********************************************************* +// +// Copyright (c) Microsoft. All rights reserved. +// This code is licensed under the MIT License. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +// PARTICULAR PURPOSE AND NONINFRINGEMENT. +// +//********************************************************* + +#pragma once +#ifndef __WIL_CPPWINRT_AUTHORING_INCLUDED +#define __WIL_CPPWINRT_AUTHORING_INCLUDED + +namespace wil +{ + template + struct read_only_property + { + read_only_property(T value) : m_value(value) { } + + operator T() const noexcept { return m_value; } + const T& operator()() const noexcept { return m_value; } + + read_only_property(const read_only_property& other) noexcept : m_value(other.m_value) { } + read_only_property(read_only_property&& other) noexcept : m_value(std::move(other.m_value)) { } + + template + read_only_property(TArgs&&... args) : m_value(std::forward(args)...) { } + + protected: + T m_value; + }; + + template + struct read_write_property : read_only_property + { + read_write_property(T value) : read_only_property(value) { } + + template + read_write_property(TArgs&&... args) : read_only_property(std::forward(args)...) { } + + using read_only_property::operator T; + using read_only_property::operator(); + read_write_property& operator()(const T& value) noexcept + { + this->m_value = value; + return *this; + } + + read_write_property& operator=(T value) noexcept + { + this->m_value = value; + return *this; + } + + template>> + read_write_property& operator=(const read_only_property& other) noexcept + { + this->m_value = other.m_value; + return *this; + } + + template>> + read_write_property& operator=(read_write_property&& other) noexcept + { + this->m_value = std::move(other.m_value); + return *this; + } + }; + +#pragma region WinRT / XAML helpers +#ifdef WINRT_Windows_Foundation_H + + namespace details { + template + struct event_base { + winrt::event_token operator()(T const& handler) { + return m_handler.add(handler); + } + auto operator()(const winrt::event_token& token) noexcept { return m_handler.remove(token); } + + template + auto invoke(TArgs&&... args) { + return m_handler(args...); + } + private: + winrt::event m_handler; + }; + } + + /** + * @brief A default event handler that maps to [Windows.Foundation.EventHandler](https://docs.microsoft.com/uwp/api/windows.foundation.eventhandler-1). + * @tparam T The event data type. + */ + template + struct simple_event : details::event_base> {}; + + /** + * @brief A default event handler that maps to [Windows.Foundation.TypedEventHandler](https://docs.microsoft.com/uwp/api/windows.foundation.typedeventhandler-2). + * @tparam T The event data type. + * @details Usage example: + * @code + * // In IDL, this corresponds to: + * // event Windows.Foundation.TypedEventHandler OkClicked; + * wil::typed_event OkClicked; + * @endcode + */ + template + struct typed_event : details::event_base> {}; + +#endif +#pragma endregion + +#pragma region INotifyPropertyChanged helpers +#ifdef WINRT_Windows_UI_Xaml_Data_H + + /** + * @brief Helper base class to inherit from to have a simple implementation of [INotifyPropertyChanged](https://docs.microsoft.com/uwp/api/windows.ui.xaml.data.inotifypropertychanged). + * @tparam T CRTP type + * @details When you declare your class, make this class a base class and pass your class as a template parameter: + * @code + * struct MyPage : MyPageT, wil::notify_property_changed_base { + * wil::property_with_notify MyInt; + * + * MyPage() : INIT_PROPERTY(MyInt, 42) { } + * }; + * @endcode + */ + template> + > + struct notify_property_changed_base { + public: + using Type = T; + auto PropertyChanged(Xaml_Data_PropertyChangedEventHandler const& value) { + return m_propertyChanged.add(value); + } + void PropertyChanged(winrt::event_token const& token) { + m_propertyChanged.remove(token); + } + Type& self() { + return *static_cast(this); + } + + /** + * @brief Raises a property change notification event + * @param name The name of the property + * @return + * @details Usage example\n + * C++ + * @code + * void MyPage::DoSomething() { + * // modify MyInt + * // MyInt = ... + * + * // now send a notification to update the bound UI elements + * RaisePropertyChanged(L"MyInt"); + * } + * @endcode + */ + auto RaisePropertyChanged(std::wstring_view name) { + return m_propertyChanged(self(), Xaml_Data_PropertyChangedEventHandler{ name }); + } + protected: + winrt::event m_propertyChanged; + }; + + /** + * @brief Implements a property type with notifications + * @tparam T the property type + * @details Use the #INIT_NOTIFY_PROPERTY macro to initialize this property in your class constructor. This will set up the right property name, and bind it to the `notify_property_changed_base` implementation. + */ + template + struct property_with_notify : read_write_property { + using Type = T; + + using read_write_property::operator(); + + void operator()(const T& value) { + if (value != operator()()) { + read_write_property::operator()(value); + if (m_npc) { + (*m_npc)(m_sender, Xaml_Data_PropertyChangedEventArgs{ m_name }); + } + } + } + template + property_with_notify( + winrt::event* npc, + winrt::Windows::Foundation::IInspectable sender, + std::wstring_view name, + TArgs&&... args) : + read_write_property(std::forward(args)...) { + m_name = name; + m_npc = npc; + m_sender = sender; + } + + property_with_notify(const property_with_notify&) = default; + property_with_notify(property_with_notify&&) = default; + private: + std::wstring m_name; + winrt::event* m_npc; + winrt::Windows::Foundation::IInspectable m_sender; + }; + + /** + * @def INIT_NOTIFY_PROPERTY + * @brief use this to initialize a wil::property_with_notify in your class constructor. + */ +#define INIT_NOTIFY_PROPERTY(NAME, VALUE) \ + NAME(&m_propertyChanged, *this, std::wstring_view{ L#NAME }, VALUE) + +#endif // WINRT_Windows_UI_Xaml_Data_H +#pragma endregion // INotifyPropertyChanged helpers +} // namespace wil +#endif // __WIL_CPPWINRT_AUTHORING_INCLUDED \ No newline at end of file diff --git a/include/wil/property.h b/include/wil/property.h deleted file mode 100644 index 2f539226..00000000 --- a/include/wil/property.h +++ /dev/null @@ -1,226 +0,0 @@ -//********************************************************* -// -// Copyright (c) Microsoft. All rights reserved. -// This code is licensed under the MIT License. -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -// PARTICULAR PURPOSE AND NONINFRINGEMENT. -// -//********************************************************* - -#pragma once -#ifndef __WIL_PROPERTY_INCLUDED -#define __WIL_PROPERTY_INCLUDED - -namespace wil -{ - -template -struct read_only_property -{ - read_only_property(T value) : m_value(value) { } - - operator T() const noexcept { return m_value; } - const T& operator()() const noexcept { return m_value; } - - read_only_property(const read_only_property& other) noexcept : m_value(other.m_value) { } - read_only_property(read_only_property&& other) noexcept : m_value(std::move(other.m_value)) { } - - template - read_only_property(TArgs&&... args) : m_value(std::forward(args)...) { } - - protected: - T m_value; -}; - -template -struct read_write_property : read_only_property -{ - read_write_property(T value) : read_only_property(value) { } - - template - read_write_property(TArgs&&... args) : read_only_property(std::forward(args)...) { } - - using read_only_property::operator T; - using read_only_property::operator(); - read_write_property& operator()(const T& value) noexcept - { - this->m_value = value; - return *this; - } - - read_write_property& operator=(T value) noexcept - { - this->m_value = value; - return *this; - } - - template>> - read_write_property& operator=(const read_only_property& other) noexcept - { - this->m_value = other.m_value; - return *this; - } - - template>> - read_write_property& operator=(read_write_property&& other) noexcept - { - this->m_value = std::move(other.m_value); - return *this; - } -}; - -#pragma region WinRT / XAML helpers -#ifdef WINRT_Windows_Foundation_H - -namespace details { - template - struct event_base { - winrt::event_token operator()(T const& handler) { - return m_handler.add(handler); - } - auto operator()(const winrt::event_token& token) noexcept { return m_handler.remove(token); } - - template - auto invoke(TArgs&&... args) { - return m_handler(args...); - } - private: - winrt::event m_handler; - }; -} - -/** - * @brief A default event handler that maps to [Windows.Foundation.EventHandler](https://docs.microsoft.com/uwp/api/windows.foundation.eventhandler-1). - * @tparam T The event data type. -*/ -template -struct simple_event : details::event_base> {}; - -/** - * @brief A default event handler that maps to [Windows.Foundation.TypedEventHandler](https://docs.microsoft.com/uwp/api/windows.foundation.typedeventhandler-2). - * @tparam T The event data type. - * @details Usage example: - * @code - * // In IDL, this corresponds to: - * // event Windows.Foundation.TypedEventHandler OkClicked; - * wil::typed_event OkClicked; - * @endcode -*/ -template -struct typed_event : details::event_base> {}; - -#endif -#pragma endregion - -#pragma region INotifyPropertyChanged helpers -#ifdef WINRT_Windows_UI_Xaml_Data_H - -/** - * @brief Helper base class to inherit from to have a simple implementation of [INotifyPropertyChanged](https://docs.microsoft.com/uwp/api/windows.ui.xaml.data.inotifypropertychanged). - * @tparam T CRTP type - * @details When you declare your class, make this class a base class and pass your class as a template parameter: - * @code - * struct MyPage : MyPageT, wil::notify_property_changed_base { - * wil::property_with_notify MyInt; - * - * MyPage() : INIT_PROPERTY(MyInt, 42) { } - * }; - * @endcode -*/ -template> -> -struct notify_property_changed_base { -public: - using Type = T; - auto PropertyChanged(Xaml_Data_PropertyChangedEventHandler const& value) { - return m_propertyChanged.add(value); - } - void PropertyChanged(winrt::event_token const& token) { - m_propertyChanged.remove(token); - } - Type& self() { - return *static_cast(this); - } - - /** - * @brief Raises a property change notification event - * @param name The name of the property - * @return - * @details Usage example\n - * C++ - * @code - * void MyPage::DoSomething() { - * // modify MyInt - * // MyInt = ... - * - * // now send a notification to update the bound UI elements - * RaisePropertyChanged(L"MyInt"); - * } - * @endcode - */ - auto RaisePropertyChanged(std::wstring_view name) { - return m_propertyChanged(self(), Xaml_Data_PropertyChangedEventHandler{ name }); - } -protected: - winrt::event m_propertyChanged; -}; - -/** - * @brief Implements a property type with notifications - * @tparam T the property type - * @details Use the #INIT_NOTIFY_PROPERTY macro to initialize this property in your class constructor. This will set up the right property name, and bind it to the `notify_property_changed_base` implementation. -*/ -template -struct property_with_notify : read_write_property { - using Type = T; - - using read_write_property::operator(); - - void operator()(const T& value) { - if (value != operator()()) { - read_write_property::operator()(value); - if (m_npc) { - (*m_npc)(m_sender, Xaml_Data_PropertyChangedEventArgs{ m_name }); - } - } - } - template - property_with_notify( - winrt::event* npc, - winrt::Windows::Foundation::IInspectable sender, - std::wstring_view name, - TArgs&&... args) : - read_write_property(std::forward(args)...) { - m_name = name; - m_npc = npc; - m_sender = sender; - } - - property_with_notify(const property_with_notify&) = default; - property_with_notify(property_with_notify&&) = default; -private: - std::wstring m_name; - winrt::event* m_npc; - winrt::Windows::Foundation::IInspectable m_sender; -}; - -/** -* @def INIT_NOTIFY_PROPERTY -* @brief use this to initialize a wil::property_with_notify in your class constructor. -*/ -#define INIT_NOTIFY_PROPERTY(NAME, VALUE) \ - NAME(&m_propertyChanged, *this, std::wstring_view{ L#NAME }, VALUE) - - -#endif - -#pragma endregion // INotifyPropertyChanged helpers - -} // namespace wil -#endif // __WIL_PROPERTY_INCLUDED \ No newline at end of file diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 32c94b85..ccb6cab2 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -52,7 +52,6 @@ set(COMMON_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/ComTests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/FileSystemTests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/NTResultTests.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/PropertyTests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ResourceTests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ResultTests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Rpc.cpp diff --git a/tests/PropertyTests.cpp b/tests/CppWinRTAuthoringTests.cpp similarity index 79% rename from tests/PropertyTests.cpp rename to tests/CppWinRTAuthoringTests.cpp index 926fcbae..4c8929ef 100644 --- a/tests/PropertyTests.cpp +++ b/tests/CppWinRTAuthoringTests.cpp @@ -1,13 +1,14 @@ #include "common.h" +#undef GetCurrentTime // check if at least C++17 #if _MSVC_LANG >= 201703L #include #include #endif -#include +#include -TEST_CASE("PropertyTests::ReadOnly", "[property]") +TEST_CASE("CppWinRTAuthoringTests::ReadOnly", "[property]") { int value = 42; wil::read_only_property prop(value); @@ -23,7 +24,7 @@ TEST_CASE("PropertyTests::ReadOnly", "[property]") REQUIRE(prop2 == prop); } -TEST_CASE("PropertyTests::ReadWrite", "[property]") +TEST_CASE("CppWinRTAuthoringTests::ReadWrite", "[property]") { int value = 42; wil::read_write_property prop(value); @@ -46,7 +47,7 @@ TEST_CASE("PropertyTests::ReadWrite", "[property]") REQUIRE(prop2 == prop2); } -TEST_CASE("PropertyTests::ReadWriteFromReadOnly", "[property]") +TEST_CASE("CppWinRTAuthoringTests::ReadWriteFromReadOnly", "[property]") { int value = 42; wil::read_only_property prop(value); @@ -69,7 +70,7 @@ TEST_CASE("PropertyTests::ReadWriteFromReadOnly", "[property]") REQUIRE(prop2 == prop2); } -TEST_CASE("PropertyTests::InStruct", "[property]") +TEST_CASE("CppWinRTAuthoringTests::InStruct", "[property]") { struct TestStruct { @@ -99,7 +100,7 @@ TEST_CASE("PropertyTests::InStruct", "[property]") } #ifdef WINRT_Windows_Foundation_H -TEST_CASE("PropertyTests::Events", "[property]") +TEST_CASE("CppWinRTAuthoringTests::Events", "[property]") { struct Test { @@ -123,15 +124,13 @@ TEST_CASE("PropertyTests::Events", "[property]") #include -TEST_CASE("PropertyTests::NotifyPropertyChanged", "[property]") +TEST_CASE("CppWinRTAuthoringTests::NotifyPropertyChanged", "[property]") { - winrt::init_apartment(winrt::apartment_type::single_threaded); + winrt::init_apartment(winrt::apartment_type::single_threaded); - // We need to initialize the XAML core in order to instantiate a PropertyChangedEventArgs [sigh]. - // This is a bit of a hack, but it works. - winrt::Windows::UI::Xaml::Hosting::DesktopWindowXamlSource dwxs; - - + // We need to initialize the XAML core in order to instantiate a PropertyChangedEventArgs [sigh]. + // This is a bit of a hack, but it works. + winrt::Windows::UI::Xaml::Hosting::DesktopWindowXamlSource dwxs; struct Test : winrt::implements, wil::notify_property_changed_base { @@ -143,15 +142,14 @@ TEST_CASE("PropertyTests::NotifyPropertyChanged", "[property]") auto testImpl = winrt::get_self(test); auto token = test.PropertyChanged([&](winrt::Windows::Foundation::IInspectable, winrt::Windows::UI::Xaml::Data::PropertyChangedEventArgs args) - { + { REQUIRE(args.PropertyName() == L"MyProperty"); REQUIRE(testImpl->MyProperty() == 43); - }); + }); testImpl->MyProperty(43); + test.PropertyChanged(token); - - winrt::uninit_apartment(); - + winrt::uninit_apartment(); } #endif // msvc \ No newline at end of file diff --git a/tests/cpplatest/CMakeLists.txt b/tests/cpplatest/CMakeLists.txt index a2f1504f..07b5ef57 100644 --- a/tests/cpplatest/CMakeLists.txt +++ b/tests/cpplatest/CMakeLists.txt @@ -23,7 +23,7 @@ target_sources(witest.cpplatest PRIVATE ${COROUTINE_SOURCES} ${CMAKE_CURRENT_SOURCE_DIR}/../CppWinRTTests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../CppWinRT20Tests.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/../PropertyTests.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../CppWinRTAuthoringTests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../StlTests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../TokenHelpersTests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../UniqueWinRTEventTokenTests.cpp From 30711f6d0ff5e94c8bceb4ebfdbb85f034f4ba2b Mon Sep 17 00:00:00 2001 From: Alexander Sklar Date: Sun, 9 Apr 2023 16:18:02 -0700 Subject: [PATCH 05/21] add /manifest:embed --- tests/cpplatest/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/cpplatest/CMakeLists.txt b/tests/cpplatest/CMakeLists.txt index 07b5ef57..c9935ede 100644 --- a/tests/cpplatest/CMakeLists.txt +++ b/tests/cpplatest/CMakeLists.txt @@ -15,7 +15,7 @@ else() endif() if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC") - target_link_options(witest.cpplatest PRIVATE /manifestinput:${CMAKE_CURRENT_SOURCE_DIR}/app.manifest) + target_link_options(witest.cpplatest PRIVATE /manifest:embed /manifestinput:${CMAKE_CURRENT_SOURCE_DIR}/app.manifest) endif() target_sources(witest.cpplatest PRIVATE From c9ea8a58f53408d2eaa25f83d1f083a74f67e1f8 Mon Sep 17 00:00:00 2001 From: Alexander Sklar Date: Thu, 13 Apr 2023 10:11:10 -0700 Subject: [PATCH 06/21] pr feedback --- include/wil/cppwinrt_authoring.h | 57 +++++++++++++++----------------- tests/CMakeLists.txt | 16 ++++----- tests/CppWinRTAuthoringTests.cpp | 22 ++++++------ 3 files changed, 46 insertions(+), 49 deletions(-) diff --git a/include/wil/cppwinrt_authoring.h b/include/wil/cppwinrt_authoring.h index 16cb2421..478ed686 100644 --- a/include/wil/cppwinrt_authoring.h +++ b/include/wil/cppwinrt_authoring.h @@ -16,62 +16,61 @@ namespace wil { template - struct read_only_property + struct single_threaded_ro_property { - read_only_property(T value) : m_value(value) { } + single_threaded_ro_property(T value) : m_value(value) { } operator T() const noexcept { return m_value; } const T& operator()() const noexcept { return m_value; } - read_only_property(const read_only_property& other) noexcept : m_value(other.m_value) { } - read_only_property(read_only_property&& other) noexcept : m_value(std::move(other.m_value)) { } + single_threaded_ro_property(const single_threaded_ro_property& other) noexcept : m_value(other.m_value) { } + single_threaded_ro_property(single_threaded_ro_property&& other) noexcept : m_value(std::move(other.m_value)) { } template - read_only_property(TArgs&&... args) : m_value(std::forward(args)...) { } + single_threaded_ro_property(TArgs&&... args) : m_value(std::forward(args)...) { } protected: T m_value; }; template - struct read_write_property : read_only_property + struct single_threaded_rw_property : single_threaded_ro_property { - read_write_property(T value) : read_only_property(value) { } + single_threaded_rw_property(T value) : single_threaded_ro_property(value) { } template - read_write_property(TArgs&&... args) : read_only_property(std::forward(args)...) { } + single_threaded_rw_property(TArgs&&... args) : single_threaded_ro_property(std::forward(args)...) { } - using read_only_property::operator T; - using read_only_property::operator(); - read_write_property& operator()(const T& value) noexcept + using single_threaded_ro_property::operator T; + using single_threaded_ro_property::operator(); + single_threaded_rw_property& operator()(const T& value) noexcept { this->m_value = value; return *this; } - read_write_property& operator=(T value) noexcept + single_threaded_rw_property& operator=(T value) noexcept { this->m_value = value; return *this; } template>> - read_write_property& operator=(const read_only_property& other) noexcept + single_threaded_rw_property& operator=(const single_threaded_ro_property& other) noexcept { this->m_value = other.m_value; return *this; } template>> - read_write_property& operator=(read_write_property&& other) noexcept + single_threaded_rw_property& operator=(single_threaded_rw_property&& other) noexcept { this->m_value = std::move(other.m_value); return *this; } }; -#pragma region WinRT / XAML helpers -#ifdef WINRT_Windows_Foundation_H +#ifdef WINRT_Windows_Foundation_H // WinRT / XAML helpers namespace details { template @@ -111,10 +110,8 @@ namespace wil struct typed_event : details::event_base> {}; #endif -#pragma endregion -#pragma region INotifyPropertyChanged helpers -#ifdef WINRT_Windows_UI_Xaml_Data_H +#ifdef WINRT_Windows_UI_Xaml_Data_H // INotifyPropertyChanged helpers /** * @brief Helper base class to inherit from to have a simple implementation of [INotifyPropertyChanged](https://docs.microsoft.com/uwp/api/windows.ui.xaml.data.inotifypropertychanged). @@ -122,7 +119,7 @@ namespace wil * @details When you declare your class, make this class a base class and pass your class as a template parameter: * @code * struct MyPage : MyPageT, wil::notify_property_changed_base { - * wil::property_with_notify MyInt; + * wil::single_threaded_notifying_property MyInt; * * MyPage() : INIT_PROPERTY(MyInt, 42) { } * }; @@ -176,33 +173,33 @@ namespace wil template - struct property_with_notify : read_write_property { + struct single_threaded_notifying_property : single_threaded_rw_property { using Type = T; - using read_write_property::operator(); + using single_threaded_rw_property::operator(); void operator()(const T& value) { - if (value != operator()()) { - read_write_property::operator()(value); + if (value != this->m_value) { + single_threaded_rw_property::operator()(value); if (m_npc) { (*m_npc)(m_sender, Xaml_Data_PropertyChangedEventArgs{ m_name }); } } } template - property_with_notify( + single_threaded_notifying_property( winrt::event* npc, winrt::Windows::Foundation::IInspectable sender, std::wstring_view name, TArgs&&... args) : - read_write_property(std::forward(args)...) { + single_threaded_rw_property(std::forward(args)...) { m_name = name; m_npc = npc; m_sender = sender; } - property_with_notify(const property_with_notify&) = default; - property_with_notify(property_with_notify&&) = default; + single_threaded_notifying_property(const single_threaded_notifying_property&) = default; + single_threaded_notifying_property(single_threaded_notifying_property&&) = default; private: std::wstring m_name; winrt::event* m_npc; @@ -211,12 +208,12 @@ namespace wil /** * @def INIT_NOTIFY_PROPERTY - * @brief use this to initialize a wil::property_with_notify in your class constructor. + * @brief use this to initialize a wil::single_threaded_notifying_property in your class constructor. */ #define INIT_NOTIFY_PROPERTY(NAME, VALUE) \ NAME(&m_propertyChanged, *this, std::wstring_view{ L#NAME }, VALUE) #endif // WINRT_Windows_UI_Xaml_Data_H -#pragma endregion // INotifyPropertyChanged helpers + } // namespace wil #endif // __WIL_CPPWINRT_AUTHORING_INCLUDED \ No newline at end of file diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index ccb6cab2..ff5a1127 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -41,10 +41,10 @@ endif() # For some unknown reason, 'RelWithDebInfo' compiles with '/Ob1' as opposed to '/Ob2' which prevents inlining of # functions not marked 'inline'. The reason we prefer 'RelWithDebInfo' over 'Release' is to get debug info, so manually # revert to the desired (and default) inlining behavior as that exercises more interesting code paths -# if (${CMAKE_BUILD_TYPE} STREQUAL "RelWithDebInfo") -# # TODO: This is currently blocked by an apparent Clang bug: https://github.com/llvm/llvm-project/issues/59690 -# # replace_cxx_flag("/Ob1" "/Ob2") -# endif() +if (${CMAKE_BUILD_TYPE} STREQUAL "RelWithDebInfo") + # TODO: This is currently blocked by an apparent Clang bug: https://github.com/llvm/llvm-project/issues/59690 + # replace_cxx_flag("/Ob1" "/Ob2") +endif() set(COMMON_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp @@ -73,12 +73,12 @@ add_subdirectory(win7) set(DEBUG_BUILD FALSE) set(HAS_DEBUG_INFO FALSE) -#if (${CMAKE_BUILD_TYPE} STREQUAL "Debug") +if (${CMAKE_BUILD_TYPE} STREQUAL "Debug") set(DEBUG_BUILD TRUE) set(HAS_DEBUG_INFO TRUE) -#elseif(${CMAKE_BUILD_TYPE} STREQUAL "RelWithDebInfo") -# set(HAS_DEBUG_INFO TRUE) -#endif() +elseif(${CMAKE_BUILD_TYPE} STREQUAL "RelWithDebInfo") + set(HAS_DEBUG_INFO TRUE) +endif() set(ASAN_AVAILABLE FALSE) set(UBSAN_AVAILABLE FALSE) diff --git a/tests/CppWinRTAuthoringTests.cpp b/tests/CppWinRTAuthoringTests.cpp index 4c8929ef..4f372e29 100644 --- a/tests/CppWinRTAuthoringTests.cpp +++ b/tests/CppWinRTAuthoringTests.cpp @@ -11,13 +11,13 @@ TEST_CASE("CppWinRTAuthoringTests::ReadOnly", "[property]") { int value = 42; - wil::read_only_property prop(value); + wil::single_threaded_ro_property prop(value); REQUIRE(prop == value); REQUIRE(prop() == value); REQUIRE(prop == prop()); REQUIRE(prop == prop); - wil::read_only_property prop2 = prop; + wil::single_threaded_ro_property prop2 = prop; REQUIRE(prop2 == value); REQUIRE(prop2() == value); REQUIRE(prop2 == prop()); @@ -27,13 +27,13 @@ TEST_CASE("CppWinRTAuthoringTests::ReadOnly", "[property]") TEST_CASE("CppWinRTAuthoringTests::ReadWrite", "[property]") { int value = 42; - wil::read_write_property prop(value); + wil::single_threaded_rw_property prop(value); REQUIRE(prop == value); REQUIRE(prop() == value); REQUIRE(prop == prop()); REQUIRE(prop == prop); - wil::read_write_property prop2 = prop; + wil::single_threaded_rw_property prop2 = prop; REQUIRE(prop2 == value); REQUIRE(prop2() == value); REQUIRE(prop2 == prop()); @@ -50,13 +50,13 @@ TEST_CASE("CppWinRTAuthoringTests::ReadWrite", "[property]") TEST_CASE("CppWinRTAuthoringTests::ReadWriteFromReadOnly", "[property]") { int value = 42; - wil::read_only_property prop(value); + wil::single_threaded_ro_property prop(value); REQUIRE(prop == value); REQUIRE(prop() == value); REQUIRE(prop == prop()); REQUIRE(prop == prop); - wil::read_write_property prop2 = prop; + wil::single_threaded_rw_property prop2 = prop; REQUIRE(prop2 == value); REQUIRE(prop2() == value); REQUIRE(prop2 == prop()); @@ -74,13 +74,13 @@ TEST_CASE("CppWinRTAuthoringTests::InStruct", "[property]") { struct TestStruct { - wil::read_only_property Prop1{ 42 }; - wil::read_write_property Prop2{ 1 }; - wil::read_only_property Prop3{ 44 }; + wil::single_threaded_ro_property Prop1{ 42 }; + wil::single_threaded_rw_property Prop2{ 1 }; + wil::single_threaded_ro_property Prop3{ 44 }; }; TestStruct test; - static_assert(!std::is_assignable_v, int>, "cannot assign to a readonly property"); + static_assert(!std::is_assignable_v, int>, "cannot assign to a readonly property"); test.Prop2 = 43; static_assert(!std::is_assignable_v, "cannot assign to a readonly property"); @@ -135,7 +135,7 @@ TEST_CASE("CppWinRTAuthoringTests::NotifyPropertyChanged", "[property]") struct Test : winrt::implements, wil::notify_property_changed_base { using wil::notify_property_changed_base::PropertyChanged; - wil::property_with_notify MyProperty; + wil::single_threaded_notifying_property MyProperty; Test() : INIT_NOTIFY_PROPERTY(MyProperty, 42) {} }; auto test = winrt::make(); From 47040bb4fb2b441c7db07b52f2765a24a9334e36 Mon Sep 17 00:00:00 2001 From: Alexander Sklar Date: Thu, 13 Apr 2023 10:19:27 -0700 Subject: [PATCH 07/21] make includeable multiple time to light up --- include/wil/cppwinrt_authoring.h | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/include/wil/cppwinrt_authoring.h b/include/wil/cppwinrt_authoring.h index 478ed686..106fb847 100644 --- a/include/wil/cppwinrt_authoring.h +++ b/include/wil/cppwinrt_authoring.h @@ -9,12 +9,11 @@ // //********************************************************* -#pragma once +namespace wil +{ #ifndef __WIL_CPPWINRT_AUTHORING_INCLUDED #define __WIL_CPPWINRT_AUTHORING_INCLUDED -namespace wil -{ template struct single_threaded_ro_property { @@ -70,8 +69,10 @@ namespace wil } }; -#ifdef WINRT_Windows_Foundation_H // WinRT / XAML helpers +#endif // __WIL_CPPWINRT_AUTHORING_INCLUDED +#if !defined(__WIL_CPPWINRT_AUTHORING_INCLUDED_FOUNDATION) && defined(WINRT_Windows_Foundation_H) // WinRT / XAML helpers +#define __WIL_CPPWINRT_AUTHORING_INCLUDED_FOUNDATION namespace details { template struct event_base { @@ -109,10 +110,10 @@ namespace wil template struct typed_event : details::event_base> {}; -#endif - -#ifdef WINRT_Windows_UI_Xaml_Data_H // INotifyPropertyChanged helpers +#endif // !defined(__WIL_CPPWINRT_AUTHORING_INCLUDED_FOUNDATION) && defined(WINRT_Windows_Foundation_H) +#if !defined(__WIL_CPPWINRT_AUTHORING_INCLUDED_XAML_DATA) && defined(WINRT_Windows_UI_Xaml_Data_H) // INotifyPropertyChanged helpers +#define __WIL_CPPWINRT_AUTHORING_INCLUDED_XAML_DATA /** * @brief Helper base class to inherit from to have a simple implementation of [INotifyPropertyChanged](https://docs.microsoft.com/uwp/api/windows.ui.xaml.data.inotifypropertychanged). * @tparam T CRTP type @@ -213,7 +214,6 @@ namespace wil #define INIT_NOTIFY_PROPERTY(NAME, VALUE) \ NAME(&m_propertyChanged, *this, std::wstring_view{ L#NAME }, VALUE) -#endif // WINRT_Windows_UI_Xaml_Data_H +#endif // !defined(__WIL_CPPWINRT_AUTHORING_INCLUDED_XAML_DATA) && defined(WINRT_Windows_UI_Xaml_Data_H) } // namespace wil -#endif // __WIL_CPPWINRT_AUTHORING_INCLUDED \ No newline at end of file From dc2ce5a0cebcddbbf44d6fd26dab72c9c8349296 Mon Sep 17 00:00:00 2001 From: Alexander Sklar Date: Thu, 13 Apr 2023 14:34:02 -0700 Subject: [PATCH 08/21] cmake supports manifests, huh cool! --- tests/cpplatest/CMakeLists.txt | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tests/cpplatest/CMakeLists.txt b/tests/cpplatest/CMakeLists.txt index c9935ede..f0259787 100644 --- a/tests/cpplatest/CMakeLists.txt +++ b/tests/cpplatest/CMakeLists.txt @@ -14,10 +14,6 @@ else() ${CMAKE_CURRENT_SOURCE_DIR}/../ComApartmentVariableTests.cpp) endif() -if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC") - target_link_options(witest.cpplatest PRIVATE /manifest:embed /manifestinput:${CMAKE_CURRENT_SOURCE_DIR}/app.manifest) -endif() - target_sources(witest.cpplatest PRIVATE ${COMMON_SOURCES} ${COROUTINE_SOURCES} @@ -30,4 +26,5 @@ target_sources(witest.cpplatest PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../WatcherTests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../WinRTTests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../WinVerifyTrustTest.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/app.manifest ) From 8d99df10ef771459ab1d38c853549be2adafadd3 Mon Sep 17 00:00:00 2001 From: Alexander Sklar Date: Thu, 13 Apr 2023 16:29:20 -0700 Subject: [PATCH 09/21] wil roinitialize --- tests/CppWinRTAuthoringTests.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/CppWinRTAuthoringTests.cpp b/tests/CppWinRTAuthoringTests.cpp index 4f372e29..349c5874 100644 --- a/tests/CppWinRTAuthoringTests.cpp +++ b/tests/CppWinRTAuthoringTests.cpp @@ -7,6 +7,7 @@ #endif #include +#include TEST_CASE("CppWinRTAuthoringTests::ReadOnly", "[property]") { @@ -126,7 +127,8 @@ TEST_CASE("CppWinRTAuthoringTests::Events", "[property]") TEST_CASE("CppWinRTAuthoringTests::NotifyPropertyChanged", "[property]") { - winrt::init_apartment(winrt::apartment_type::single_threaded); +#if defined(WIL_ENABLE_EXCEPTIONS) + auto init = wil::RoInitialize_failfast(); // We need to initialize the XAML core in order to instantiate a PropertyChangedEventArgs [sigh]. // This is a bit of a hack, but it works. @@ -149,7 +151,6 @@ TEST_CASE("CppWinRTAuthoringTests::NotifyPropertyChanged", "[property]") testImpl->MyProperty(43); test.PropertyChanged(token); - - winrt::uninit_apartment(); +#endif } #endif // msvc \ No newline at end of file From 831542d27b3c9706f862232c0c25a9f3b23271c5 Mon Sep 17 00:00:00 2001 From: Alexander Sklar Date: Thu, 13 Apr 2023 20:10:35 -0700 Subject: [PATCH 10/21] tests passing --- tests/CppWinRTAuthoringTests.cpp | 11 +++++++---- tests/FileSystemTests.cpp | 2 +- tests/WinRTTests.cpp | 8 ++++---- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/tests/CppWinRTAuthoringTests.cpp b/tests/CppWinRTAuthoringTests.cpp index 349c5874..8ae81324 100644 --- a/tests/CppWinRTAuthoringTests.cpp +++ b/tests/CppWinRTAuthoringTests.cpp @@ -1,3 +1,4 @@ +#define WINAPI_PARTITION_DESKTOP 1 // for RO_INIT_SINGLETHREADED #include "common.h" #undef GetCurrentTime // check if at least C++17 @@ -128,11 +129,10 @@ TEST_CASE("CppWinRTAuthoringTests::Events", "[property]") TEST_CASE("CppWinRTAuthoringTests::NotifyPropertyChanged", "[property]") { #if defined(WIL_ENABLE_EXCEPTIONS) - auto init = wil::RoInitialize_failfast(); + auto uninit = wil::RoInitialize_failfast(RO_INIT_SINGLETHREADED); - // We need to initialize the XAML core in order to instantiate a PropertyChangedEventArgs [sigh]. - // This is a bit of a hack, but it works. - winrt::Windows::UI::Xaml::Hosting::DesktopWindowXamlSource dwxs; + // We need to initialize the XAML core in order to instantiate a PropertyChangedEventArgs. + auto manager = winrt::Windows::UI::Xaml::Hosting::WindowsXamlManager::InitializeForCurrentThread(); struct Test : winrt::implements, wil::notify_property_changed_base { @@ -151,6 +151,9 @@ TEST_CASE("CppWinRTAuthoringTests::NotifyPropertyChanged", "[property]") testImpl->MyProperty(43); test.PropertyChanged(token); + + manager.Close(); + #endif } #endif // msvc \ No newline at end of file diff --git a/tests/FileSystemTests.cpp b/tests/FileSystemTests.cpp index a6e8b5ca..d7d03de0 100644 --- a/tests/FileSystemTests.cpp +++ b/tests/FileSystemTests.cpp @@ -610,7 +610,7 @@ TEST_CASE("FileSystemTests::QueryFullProcessImageNameW and GetModuleFileNameW", #ifdef WIL_ENABLE_EXCEPTIONS auto procName = wil::QueryFullProcessImageNameW(); auto moduleName = wil::GetModuleFileNameW(); - REQUIRE(procName == moduleName); + REQUIRE(CompareStringOrdinal(procName.c_str(), -1, moduleName.c_str(), -1, TRUE) == CSTR_EQUAL); #endif } diff --git a/tests/WinRTTests.cpp b/tests/WinRTTests.cpp index 4e1ecf2a..592494a5 100644 --- a/tests/WinRTTests.cpp +++ b/tests/WinRTTests.cpp @@ -1,4 +1,4 @@ - +#define WINAPI_PARTITION_DESKTOP 1 // for RO_INIT_SINGLETHREADED #include #ifdef WIL_ENABLE_EXCEPTIONS @@ -699,7 +699,7 @@ template auto cast_to(ComPtr const& src) TEST_CASE("WinRTTests::VectorToVectorTest", "[winrt][to_vector]") { #if defined(WIL_ENABLE_EXCEPTIONS) - auto uninit = wil::RoInitialize_failfast(); + auto uninit = wil::RoInitialize_failfast(RO_INIT_SINGLETHREADED); auto ints = MakeSampleInspectableVector(100); auto vec = wil::to_vector(ints.Get()); UINT32 size; @@ -716,7 +716,7 @@ TEST_CASE("WinRTTests::VectorToVectorTest", "[winrt][to_vector]") TEST_CASE("WinRTTests::VectorRangeTest", "[winrt][vector_range]") { - auto uninit = wil::RoInitialize_failfast(); + auto uninit = wil::RoInitialize_failfast(RO_INIT_SINGLETHREADED); auto inspectables = MakeSampleInspectableVector(); unsigned count = 0; @@ -865,7 +865,7 @@ unsigned long GetComObjectRefCount(IUnknown* unk) { unk->AddRef(); return unk->R TEST_CASE("WinRTTests::VectorRangeLeakTest", "[winrt][vector_range]") { - auto uninit = wil::RoInitialize_failfast(); + auto uninit = wil::RoInitialize_failfast(RO_INIT_SINGLETHREADED); auto inspectables = MakeSampleInspectableVector(); ComPtr verifyNotLeaked; From 7fcfb7c4290725d9b6bcbed2b20e5a872a55d390 Mon Sep 17 00:00:00 2001 From: Alexander Sklar Date: Thu, 13 Apr 2023 21:32:37 -0700 Subject: [PATCH 11/21] > --- tests/CppWinRTAuthoringTests.cpp | 3 +-- tests/WinRTTests.cpp | 8 ++++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/tests/CppWinRTAuthoringTests.cpp b/tests/CppWinRTAuthoringTests.cpp index 8ae81324..f62c91d4 100644 --- a/tests/CppWinRTAuthoringTests.cpp +++ b/tests/CppWinRTAuthoringTests.cpp @@ -131,7 +131,7 @@ TEST_CASE("CppWinRTAuthoringTests::NotifyPropertyChanged", "[property]") #if defined(WIL_ENABLE_EXCEPTIONS) auto uninit = wil::RoInitialize_failfast(RO_INIT_SINGLETHREADED); - // We need to initialize the XAML core in order to instantiate a PropertyChangedEventArgs. + // We need to initialize the XAML core in order to instantiate a PropertyChangedEventArgs [sigh]. auto manager = winrt::Windows::UI::Xaml::Hosting::WindowsXamlManager::InitializeForCurrentThread(); struct Test : winrt::implements, wil::notify_property_changed_base @@ -153,7 +153,6 @@ TEST_CASE("CppWinRTAuthoringTests::NotifyPropertyChanged", "[property]") test.PropertyChanged(token); manager.Close(); - #endif } #endif // msvc \ No newline at end of file diff --git a/tests/WinRTTests.cpp b/tests/WinRTTests.cpp index 592494a5..4e1ecf2a 100644 --- a/tests/WinRTTests.cpp +++ b/tests/WinRTTests.cpp @@ -1,4 +1,4 @@ -#define WINAPI_PARTITION_DESKTOP 1 // for RO_INIT_SINGLETHREADED + #include #ifdef WIL_ENABLE_EXCEPTIONS @@ -699,7 +699,7 @@ template auto cast_to(ComPtr const& src) TEST_CASE("WinRTTests::VectorToVectorTest", "[winrt][to_vector]") { #if defined(WIL_ENABLE_EXCEPTIONS) - auto uninit = wil::RoInitialize_failfast(RO_INIT_SINGLETHREADED); + auto uninit = wil::RoInitialize_failfast(); auto ints = MakeSampleInspectableVector(100); auto vec = wil::to_vector(ints.Get()); UINT32 size; @@ -716,7 +716,7 @@ TEST_CASE("WinRTTests::VectorToVectorTest", "[winrt][to_vector]") TEST_CASE("WinRTTests::VectorRangeTest", "[winrt][vector_range]") { - auto uninit = wil::RoInitialize_failfast(RO_INIT_SINGLETHREADED); + auto uninit = wil::RoInitialize_failfast(); auto inspectables = MakeSampleInspectableVector(); unsigned count = 0; @@ -865,7 +865,7 @@ unsigned long GetComObjectRefCount(IUnknown* unk) { unk->AddRef(); return unk->R TEST_CASE("WinRTTests::VectorRangeLeakTest", "[winrt][vector_range]") { - auto uninit = wil::RoInitialize_failfast(RO_INIT_SINGLETHREADED); + auto uninit = wil::RoInitialize_failfast(); auto inspectables = MakeSampleInspectableVector(); ComPtr verifyNotLeaked; From 66bedf2e7c81c44bce97db03dd316a5bebbff7f4 Mon Sep 17 00:00:00 2001 From: Alexander Sklar Date: Thu, 13 Apr 2023 22:31:41 -0700 Subject: [PATCH 12/21] xaml must be unloaded before com rundown --- tests/CppWinRTAuthoringTests.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/CppWinRTAuthoringTests.cpp b/tests/CppWinRTAuthoringTests.cpp index f62c91d4..c27f0160 100644 --- a/tests/CppWinRTAuthoringTests.cpp +++ b/tests/CppWinRTAuthoringTests.cpp @@ -9,6 +9,7 @@ #include #include +#include TEST_CASE("CppWinRTAuthoringTests::ReadOnly", "[property]") { @@ -130,8 +131,9 @@ TEST_CASE("CppWinRTAuthoringTests::NotifyPropertyChanged", "[property]") { #if defined(WIL_ENABLE_EXCEPTIONS) auto uninit = wil::RoInitialize_failfast(RO_INIT_SINGLETHREADED); - - // We need to initialize the XAML core in order to instantiate a PropertyChangedEventArgs [sigh]. + // We need to initialize the XAML core in order to instantiate a PropertyChangedEventArgs. + // XAML needs to be unloaded before COM rundown. + auto xaml = wil::unique_hmodule(::LoadLibraryExW(L"Windows.UI.Xaml.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32)); auto manager = winrt::Windows::UI::Xaml::Hosting::WindowsXamlManager::InitializeForCurrentThread(); struct Test : winrt::implements, wil::notify_property_changed_base From 9108a1eca44160dbb3730703a20341880bdc2e1d Mon Sep 17 00:00:00 2001 From: Alexander Sklar Date: Fri, 14 Apr 2023 16:10:25 -0700 Subject: [PATCH 13/21] we cannot run the notify property test in the same process as the malloc spies one because of an issue in COM --- tests/CppWinRTAuthoringTests.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/CppWinRTAuthoringTests.cpp b/tests/CppWinRTAuthoringTests.cpp index c27f0160..5e62d5ba 100644 --- a/tests/CppWinRTAuthoringTests.cpp +++ b/tests/CppWinRTAuthoringTests.cpp @@ -127,13 +127,13 @@ TEST_CASE("CppWinRTAuthoringTests::Events", "[property]") #include -TEST_CASE("CppWinRTAuthoringTests::NotifyPropertyChanged", "[property]") +// This test cannot run in the same process as the malloc spies tests in wiTest.cpp +// MSFT_internal: https://task.ms/44191550 +TEST_CASE("CppWinRTAuthoringTests::NotifyPropertyChanged", "[property][LocalOnly]") { #if defined(WIL_ENABLE_EXCEPTIONS) auto uninit = wil::RoInitialize_failfast(RO_INIT_SINGLETHREADED); // We need to initialize the XAML core in order to instantiate a PropertyChangedEventArgs. - // XAML needs to be unloaded before COM rundown. - auto xaml = wil::unique_hmodule(::LoadLibraryExW(L"Windows.UI.Xaml.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32)); auto manager = winrt::Windows::UI::Xaml::Hosting::WindowsXamlManager::InitializeForCurrentThread(); struct Test : winrt::implements, wil::notify_property_changed_base From 6af648ce33b5b728a0383caac38d52b6a01d9f0c Mon Sep 17 00:00:00 2001 From: Alexander Sklar Date: Sun, 16 Apr 2023 15:00:50 -0700 Subject: [PATCH 14/21] pr feedback --- include/wil/cppwinrt_authoring.h | 45 +++++++++++++++++++++----------- tests/CppWinRTAuthoringTests.cpp | 2 +- 2 files changed, 31 insertions(+), 16 deletions(-) diff --git a/include/wil/cppwinrt_authoring.h b/include/wil/cppwinrt_authoring.h index 106fb847..794c3f24 100644 --- a/include/wil/cppwinrt_authoring.h +++ b/include/wil/cppwinrt_authoring.h @@ -76,14 +76,14 @@ namespace wil namespace details { template struct event_base { - winrt::event_token operator()(T const& handler) { - return m_handler.add(handler); + winrt::event_token operator()(T&& handler) { + return m_handler.add(std::forward(handler)); } auto operator()(const winrt::event_token& token) noexcept { return m_handler.remove(token); } template auto invoke(TArgs&&... args) { - return m_handler(args...); + return m_handler(std::forward(args)...); } private: winrt::event m_handler; @@ -112,8 +112,21 @@ namespace wil #endif // !defined(__WIL_CPPWINRT_AUTHORING_INCLUDED_FOUNDATION) && defined(WINRT_Windows_Foundation_H) -#if !defined(__WIL_CPPWINRT_AUTHORING_INCLUDED_XAML_DATA) && defined(WINRT_Windows_UI_Xaml_Data_H) // INotifyPropertyChanged helpers +#if !defined(__WIL_CPPWINRT_AUTHORING_INCLUDED_XAML_DATA) // INotifyPropertyChanged helpers #define __WIL_CPPWINRT_AUTHORING_INCLUDED_XAML_DATA +namespace details +{ +#ifdef WINRT_Microsoft_UI_Xaml_Data_H + using Xaml_Data_PropertyChangedEventHandler = winrt::Microsoft::UI::Xaml::Data::PropertyChangedEventHandler; + using Xaml_Data_PropertyChangedEventArgs = winrt::Microsoft::UI::Xaml::Data::PropertyChangedEventArgs; +#else if defined(Windows_UI_Xaml_Data_H) + using Xaml_Data_PropertyChangedEventHandler = winrt::Windows::UI::Xaml::Data::PropertyChangedEventHandler; + using Xaml_Data_PropertyChangedEventArgs = winrt::Windows::UI::Xaml::Data::PropertyChangedEventArgs; +#endif +} + +#if defined(WINRT_Microsoft_UI_Xaml_Data_H) || defined(WINRT_Windows_UI_Xaml_Data_H) + /** * @brief Helper base class to inherit from to have a simple implementation of [INotifyPropertyChanged](https://docs.microsoft.com/uwp/api/windows.ui.xaml.data.inotifypropertychanged). * @tparam T CRTP type @@ -127,7 +140,7 @@ namespace wil * @endcode */ template> > struct notify_property_changed_base { @@ -172,8 +185,8 @@ namespace wil * @details Use the #INIT_NOTIFY_PROPERTY macro to initialize this property in your class constructor. This will set up the right property name, and bind it to the `notify_property_changed_base` implementation. */ template + typename Xaml_Data_PropertyChangedEventHandler = details::Xaml_Data_PropertyChangedEventHandler, + typename Xaml_Data_PropertyChangedEventArgs = details::Xaml_Data_PropertyChangedEventArgs> struct single_threaded_notifying_property : single_threaded_rw_property { using Type = T; @@ -193,16 +206,17 @@ namespace wil winrt::Windows::Foundation::IInspectable sender, std::wstring_view name, TArgs&&... args) : - single_threaded_rw_property(std::forward(args)...) { - m_name = name; - m_npc = npc; - m_sender = sender; - } + single_threaded_rw_property(std::forward(args)...), + m_name(name), + m_npc(npc), + m_sender(sender) + {} single_threaded_notifying_property(const single_threaded_notifying_property&) = default; single_threaded_notifying_property(single_threaded_notifying_property&&) = default; + std::wstring_view Name() const noexcept { return m_name; } private: - std::wstring m_name; + std::wstring_view m_name; winrt::event* m_npc; winrt::Windows::Foundation::IInspectable m_sender; }; @@ -212,8 +226,9 @@ namespace wil * @brief use this to initialize a wil::single_threaded_notifying_property in your class constructor. */ #define INIT_NOTIFY_PROPERTY(NAME, VALUE) \ - NAME(&m_propertyChanged, *this, std::wstring_view{ L#NAME }, VALUE) + NAME(&m_propertyChanged, *this, (this->NAME, L#NAME), VALUE) -#endif // !defined(__WIL_CPPWINRT_AUTHORING_INCLUDED_XAML_DATA) && defined(WINRT_Windows_UI_Xaml_Data_H) +#endif // defined(WINRT_Microsoft_UI_Xaml_Data_H) || defined(WINRT_Windows_UI_Xaml_Data_H) +#endif // !defined(__WIL_CPPWINRT_AUTHORING_INCLUDED_XAML_DATA) } // namespace wil diff --git a/tests/CppWinRTAuthoringTests.cpp b/tests/CppWinRTAuthoringTests.cpp index 5e62d5ba..182c81bc 100644 --- a/tests/CppWinRTAuthoringTests.cpp +++ b/tests/CppWinRTAuthoringTests.cpp @@ -153,7 +153,7 @@ TEST_CASE("CppWinRTAuthoringTests::NotifyPropertyChanged", "[property][LocalOnly testImpl->MyProperty(43); test.PropertyChanged(token); - + REQUIRE(testImpl->MyProperty.Name() == L"MyProperty"); manager.Close(); #endif } From 5ed57904b89307b2c9d90380ae68dfe128425e40 Mon Sep 17 00:00:00 2001 From: Alexander Sklar Date: Sun, 16 Apr 2023 15:14:13 -0700 Subject: [PATCH 15/21] use is-a instead of has-a --- include/wil/cppwinrt_authoring.h | 60 ++++++++++++++------------------ tests/CppWinRTAuthoringTests.cpp | 3 ++ 2 files changed, 29 insertions(+), 34 deletions(-) diff --git a/include/wil/cppwinrt_authoring.h b/include/wil/cppwinrt_authoring.h index 794c3f24..a055a360 100644 --- a/include/wil/cppwinrt_authoring.h +++ b/include/wil/cppwinrt_authoring.h @@ -13,59 +13,51 @@ namespace wil { #ifndef __WIL_CPPWINRT_AUTHORING_INCLUDED #define __WIL_CPPWINRT_AUTHORING_INCLUDED + namespace details + { + template + struct single_threaded_property_storage + { + T m_value{}; + single_threaded_property_storage() = default; + single_threaded_property_storage(const T& value) : m_value(value) {} + operator T& () { return m_value; } + operator T const& () const { return m_value; } + template auto operator=(Q&& q) + { + m_value = wistd::forward(q); + return *this; + } + }; + } template - struct single_threaded_ro_property + struct single_threaded_ro_property : std::conditional_t, details::single_threaded_property_storage, T> { - single_threaded_ro_property(T value) : m_value(value) { } - - operator T() const noexcept { return m_value; } - const T& operator()() const noexcept { return m_value; } - - single_threaded_ro_property(const single_threaded_ro_property& other) noexcept : m_value(other.m_value) { } - single_threaded_ro_property(single_threaded_ro_property&& other) noexcept : m_value(std::move(other.m_value)) { } - - template - single_threaded_ro_property(TArgs&&... args) : m_value(std::forward(args)...) { } - - protected: - T m_value; + auto& operator()() + { + return *this; + } }; template struct single_threaded_rw_property : single_threaded_ro_property { - single_threaded_rw_property(T value) : single_threaded_ro_property(value) { } - template single_threaded_rw_property(TArgs&&... args) : single_threaded_ro_property(std::forward(args)...) { } - using single_threaded_ro_property::operator T; using single_threaded_ro_property::operator(); - single_threaded_rw_property& operator()(const T& value) noexcept - { - this->m_value = value; - return *this; - } - single_threaded_rw_property& operator=(T value) noexcept + template auto& operator()(Q&& q) { - this->m_value = value; + *this = std::forward(q); return *this; } - template>> - single_threaded_rw_property& operator=(const single_threaded_ro_property& other) noexcept - { - this->m_value = other.m_value; - return *this; - } - template>> - single_threaded_rw_property& operator=(single_threaded_rw_property&& other) noexcept + template auto operator=(Q&& q) { - this->m_value = std::move(other.m_value); - return *this; + return static_cast(*this) = std::forward(q); } }; diff --git a/tests/CppWinRTAuthoringTests.cpp b/tests/CppWinRTAuthoringTests.cpp index 182c81bc..f1e9937a 100644 --- a/tests/CppWinRTAuthoringTests.cpp +++ b/tests/CppWinRTAuthoringTests.cpp @@ -25,6 +25,9 @@ TEST_CASE("CppWinRTAuthoringTests::ReadOnly", "[property]") REQUIRE(prop2() == value); REQUIRE(prop2 == prop()); REQUIRE(prop2 == prop); + + wil::single_threaded_ro_property prop3; + REQUIRE(prop3.empty()); } TEST_CASE("CppWinRTAuthoringTests::ReadWrite", "[property]") From 279842f386610b3ea23f2665e59579cd2db2f9ea Mon Sep 17 00:00:00 2001 From: Alexander Sklar Date: Sun, 16 Apr 2023 15:41:43 -0700 Subject: [PATCH 16/21] . --- include/wil/cppwinrt_authoring.h | 16 +++++++++++++--- tests/CppWinRTAuthoringTests.cpp | 11 +++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/include/wil/cppwinrt_authoring.h b/include/wil/cppwinrt_authoring.h index a055a360..6d3a672e 100644 --- a/include/wil/cppwinrt_authoring.h +++ b/include/wil/cppwinrt_authoring.h @@ -34,10 +34,16 @@ namespace wil template struct single_threaded_ro_property : std::conditional_t, details::single_threaded_property_storage, T> { - auto& operator()() + single_threaded_ro_property() = default; + single_threaded_ro_property(const T& t) : base_type(t) { } + + const auto& operator()() { return *this; } + template auto operator=(Q&& q) = delete; + private: + using base_type = std::conditional_t, details::single_threaded_property_storage, T>; }; template @@ -48,6 +54,10 @@ namespace wil using single_threaded_ro_property::operator(); + auto& operator()() + { + return *this; + } template auto& operator()(Q&& q) { *this = std::forward(q); @@ -111,7 +121,7 @@ namespace details #ifdef WINRT_Microsoft_UI_Xaml_Data_H using Xaml_Data_PropertyChangedEventHandler = winrt::Microsoft::UI::Xaml::Data::PropertyChangedEventHandler; using Xaml_Data_PropertyChangedEventArgs = winrt::Microsoft::UI::Xaml::Data::PropertyChangedEventArgs; -#else if defined(Windows_UI_Xaml_Data_H) +#elif defined(WINRT_Windows_UI_Xaml_Data_H) using Xaml_Data_PropertyChangedEventHandler = winrt::Windows::UI::Xaml::Data::PropertyChangedEventHandler; using Xaml_Data_PropertyChangedEventArgs = winrt::Windows::UI::Xaml::Data::PropertyChangedEventArgs; #endif @@ -132,7 +142,7 @@ namespace details * @endcode */ template> > struct notify_property_changed_base { diff --git a/tests/CppWinRTAuthoringTests.cpp b/tests/CppWinRTAuthoringTests.cpp index f1e9937a..f77cfea5 100644 --- a/tests/CppWinRTAuthoringTests.cpp +++ b/tests/CppWinRTAuthoringTests.cpp @@ -51,6 +51,17 @@ TEST_CASE("CppWinRTAuthoringTests::ReadWrite", "[property]") REQUIRE(prop2() == value2); REQUIRE(prop2 == prop2()); REQUIRE(prop2 == prop2); + + wil::single_threaded_rw_property prop3("foo"); + REQUIRE(prop3 == "foo"); + REQUIRE(prop3() == "foo"); + REQUIRE(prop3.length() == 3); + prop3 = "bar"; + REQUIRE(prop3 == "bar"); + auto& prop3alias = prop3("baz"); + REQUIRE(prop3 == "baz"); + prop3alias = "foo"; + REQUIRE(prop3 == "foo"); } TEST_CASE("CppWinRTAuthoringTests::ReadWriteFromReadOnly", "[property]") From 0fd18929bc0459e272c03c2a53832f76ad7525f1 Mon Sep 17 00:00:00 2001 From: Alexander Sklar Date: Sun, 16 Apr 2023 16:07:41 -0700 Subject: [PATCH 17/21] . --- include/wil/cppwinrt_authoring.h | 14 +++++++------- tests/CppWinRTAuthoringTests.cpp | 1 - 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/include/wil/cppwinrt_authoring.h b/include/wil/cppwinrt_authoring.h index 6d3a672e..63745d5d 100644 --- a/include/wil/cppwinrt_authoring.h +++ b/include/wil/cppwinrt_authoring.h @@ -32,7 +32,7 @@ namespace wil } template - struct single_threaded_ro_property : std::conditional_t, details::single_threaded_property_storage, T> + struct single_threaded_ro_property : std::conditional_t, wil::details::single_threaded_property_storage, T> { single_threaded_ro_property() = default; single_threaded_ro_property(const T& t) : base_type(t) { } @@ -43,7 +43,7 @@ namespace wil } template auto operator=(Q&& q) = delete; private: - using base_type = std::conditional_t, details::single_threaded_property_storage, T>; + using base_type = std::conditional_t, wil::details::single_threaded_property_storage, T>; }; template @@ -97,7 +97,7 @@ namespace wil * @tparam T The event data type. */ template - struct simple_event : details::event_base> {}; + struct simple_event : wil::details::event_base> {}; /** * @brief A default event handler that maps to [Windows.Foundation.TypedEventHandler](https://docs.microsoft.com/uwp/api/windows.foundation.typedeventhandler-2). @@ -110,7 +110,7 @@ namespace wil * @endcode */ template - struct typed_event : details::event_base> {}; + struct typed_event : wil::details::event_base> {}; #endif // !defined(__WIL_CPPWINRT_AUTHORING_INCLUDED_FOUNDATION) && defined(WINRT_Windows_Foundation_H) @@ -187,8 +187,8 @@ namespace details * @details Use the #INIT_NOTIFY_PROPERTY macro to initialize this property in your class constructor. This will set up the right property name, and bind it to the `notify_property_changed_base` implementation. */ template + typename Xaml_Data_PropertyChangedEventHandler = wil::details::Xaml_Data_PropertyChangedEventHandler, + typename Xaml_Data_PropertyChangedEventArgs = wil::details::Xaml_Data_PropertyChangedEventArgs> struct single_threaded_notifying_property : single_threaded_rw_property { using Type = T; @@ -228,7 +228,7 @@ namespace details * @brief use this to initialize a wil::single_threaded_notifying_property in your class constructor. */ #define INIT_NOTIFY_PROPERTY(NAME, VALUE) \ - NAME(&m_propertyChanged, *this, (this->NAME, L#NAME), VALUE) + NAME(&m_propertyChanged, *this, L#NAME, VALUE) #endif // defined(WINRT_Microsoft_UI_Xaml_Data_H) || defined(WINRT_Windows_UI_Xaml_Data_H) diff --git a/tests/CppWinRTAuthoringTests.cpp b/tests/CppWinRTAuthoringTests.cpp index f77cfea5..8a196fe8 100644 --- a/tests/CppWinRTAuthoringTests.cpp +++ b/tests/CppWinRTAuthoringTests.cpp @@ -152,7 +152,6 @@ TEST_CASE("CppWinRTAuthoringTests::NotifyPropertyChanged", "[property][LocalOnly struct Test : winrt::implements, wil::notify_property_changed_base { - using wil::notify_property_changed_base::PropertyChanged; wil::single_threaded_notifying_property MyProperty; Test() : INIT_NOTIFY_PROPERTY(MyProperty, 42) {} }; From 87ec4a19a70b87d236cf1932aab10aa2599aa8fe Mon Sep 17 00:00:00 2001 From: Alexander Sklar Date: Mon, 17 Apr 2023 17:38:47 -0700 Subject: [PATCH 18/21] pr feedback --- include/wil/cppwinrt_authoring.h | 87 ++++++++++++++++++-------------- tests/CMakeLists.txt | 6 +-- 2 files changed, 52 insertions(+), 41 deletions(-) diff --git a/include/wil/cppwinrt_authoring.h b/include/wil/cppwinrt_authoring.h index 63745d5d..ecf97f0c 100644 --- a/include/wil/cppwinrt_authoring.h +++ b/include/wil/cppwinrt_authoring.h @@ -11,8 +11,8 @@ namespace wil { -#ifndef __WIL_CPPWINRT_AUTHORING_INCLUDED -#define __WIL_CPPWINRT_AUTHORING_INCLUDED +#ifndef __WIL_CPPWINRT_AUTHORING_PROPERTIES_INCLUDED +#define __WIL_CPPWINRT_AUTHORING_PROPERTIES_INCLUDED namespace details { template @@ -32,10 +32,10 @@ namespace wil } template - struct single_threaded_ro_property : std::conditional_t, wil::details::single_threaded_property_storage, T> + struct single_threaded_ro_property : std::conditional_t || std::is_final_v, wil::details::single_threaded_property_storage, T> { single_threaded_ro_property() = default; - single_threaded_ro_property(const T& t) : base_type(t) { } + single_threaded_ro_property(T value) : base_type(std::move(value)) { } const auto& operator()() { @@ -71,17 +71,23 @@ namespace wil } }; -#endif // __WIL_CPPWINRT_AUTHORING_INCLUDED +#endif // __WIL_CPPWINRT_AUTHORING_PROPERTIES_INCLUDED #if !defined(__WIL_CPPWINRT_AUTHORING_INCLUDED_FOUNDATION) && defined(WINRT_Windows_Foundation_H) // WinRT / XAML helpers #define __WIL_CPPWINRT_AUTHORING_INCLUDED_FOUNDATION - namespace details { + namespace details + { template struct event_base { - winrt::event_token operator()(T&& handler) { + winrt::event_token operator()(T&& handler) + { return m_handler.add(std::forward(handler)); } - auto operator()(const winrt::event_token& token) noexcept { return m_handler.remove(token); } + + auto operator()(const winrt::event_token& token) noexcept + { + return m_handler.remove(token); + } template auto invoke(TArgs&&... args) { @@ -114,20 +120,18 @@ namespace wil #endif // !defined(__WIL_CPPWINRT_AUTHORING_INCLUDED_FOUNDATION) && defined(WINRT_Windows_Foundation_H) -#if !defined(__WIL_CPPWINRT_AUTHORING_INCLUDED_XAML_DATA) // INotifyPropertyChanged helpers +#if !defined(__WIL_CPPWINRT_AUTHORING_INCLUDED_XAML_DATA) && (defined(WINRT_Microsoft_UI_Xaml_Data_H) || defined(WINRT_Windows_UI_Xaml_Data_H)) // INotifyPropertyChanged helpers #define __WIL_CPPWINRT_AUTHORING_INCLUDED_XAML_DATA -namespace details -{ + namespace details + { #ifdef WINRT_Microsoft_UI_Xaml_Data_H - using Xaml_Data_PropertyChangedEventHandler = winrt::Microsoft::UI::Xaml::Data::PropertyChangedEventHandler; - using Xaml_Data_PropertyChangedEventArgs = winrt::Microsoft::UI::Xaml::Data::PropertyChangedEventArgs; + using Xaml_Data_PropertyChangedEventHandler = winrt::Microsoft::UI::Xaml::Data::PropertyChangedEventHandler; + using Xaml_Data_PropertyChangedEventArgs = winrt::Microsoft::UI::Xaml::Data::PropertyChangedEventArgs; #elif defined(WINRT_Windows_UI_Xaml_Data_H) - using Xaml_Data_PropertyChangedEventHandler = winrt::Windows::UI::Xaml::Data::PropertyChangedEventHandler; - using Xaml_Data_PropertyChangedEventArgs = winrt::Windows::UI::Xaml::Data::PropertyChangedEventArgs; + using Xaml_Data_PropertyChangedEventHandler = winrt::Windows::UI::Xaml::Data::PropertyChangedEventHandler; + using Xaml_Data_PropertyChangedEventArgs = winrt::Windows::UI::Xaml::Data::PropertyChangedEventArgs; #endif -} - -#if defined(WINRT_Microsoft_UI_Xaml_Data_H) || defined(WINRT_Windows_UI_Xaml_Data_H) + } /** * @brief Helper base class to inherit from to have a simple implementation of [INotifyPropertyChanged](https://docs.microsoft.com/uwp/api/windows.ui.xaml.data.inotifypropertychanged). @@ -142,19 +146,22 @@ namespace details * @endcode */ template> - > - struct notify_property_changed_base { - public: + typename Xaml_Data_PropertyChangedEventHandler = wil::details::Xaml_Data_PropertyChangedEventHandler> + struct notify_property_changed_base + { using Type = T; - auto PropertyChanged(Xaml_Data_PropertyChangedEventHandler const& value) { + auto PropertyChanged(Xaml_Data_PropertyChangedEventHandler const& value) + { return m_propertyChanged.add(value); } - void PropertyChanged(winrt::event_token const& token) { + + void PropertyChanged(winrt::event_token const& token) + { m_propertyChanged.remove(token); } - Type& self() { + + Type& self() + { return *static_cast(this); } @@ -165,12 +172,13 @@ namespace details * @details Usage example\n * C++ * @code - * void MyPage::DoSomething() { - * // modify MyInt - * // MyInt = ... + * void MyPage::DoSomething() + * { + * // modify MyInt + * // MyInt = ... * - * // now send a notification to update the bound UI elements - * RaisePropertyChanged(L"MyInt"); + * // now send a notification to update the bound UI elements + * RaisePropertyChanged(L"MyInt"); * } * @endcode */ @@ -189,19 +197,24 @@ namespace details template - struct single_threaded_notifying_property : single_threaded_rw_property { + struct single_threaded_notifying_property : single_threaded_rw_property + { using Type = T; using single_threaded_rw_property::operator(); - void operator()(const T& value) { - if (value != this->m_value) { + void operator()(const T& value) + { + if (value != this->m_value) + { single_threaded_rw_property::operator()(value); - if (m_npc) { + if (m_npc) + { (*m_npc)(m_sender, Xaml_Data_PropertyChangedEventArgs{ m_name }); } } } + template single_threaded_notifying_property( winrt::event* npc, @@ -230,7 +243,5 @@ namespace details #define INIT_NOTIFY_PROPERTY(NAME, VALUE) \ NAME(&m_propertyChanged, *this, L#NAME, VALUE) -#endif // defined(WINRT_Microsoft_UI_Xaml_Data_H) || defined(WINRT_Windows_UI_Xaml_Data_H) - -#endif // !defined(__WIL_CPPWINRT_AUTHORING_INCLUDED_XAML_DATA) +#endif // !defined(__WIL_CPPWINRT_AUTHORING_INCLUDED_XAML_DATA) && (defined(WINRT_Microsoft_UI_Xaml_Data_H) || defined(WINRT_Windows_UI_Xaml_Data_H)) } // namespace wil diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index ff5a1127..7ed7baa8 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -42,8 +42,8 @@ endif() # functions not marked 'inline'. The reason we prefer 'RelWithDebInfo' over 'Release' is to get debug info, so manually # revert to the desired (and default) inlining behavior as that exercises more interesting code paths if (${CMAKE_BUILD_TYPE} STREQUAL "RelWithDebInfo") - # TODO: This is currently blocked by an apparent Clang bug: https://github.com/llvm/llvm-project/issues/59690 - # replace_cxx_flag("/Ob1" "/Ob2") + # TODO: This is currently blocked by an apparent Clang bug: https://github.com/llvm/llvm-project/issues/59690 + # replace_cxx_flag("/Ob1" "/Ob2") endif() set(COMMON_SOURCES @@ -77,7 +77,7 @@ if (${CMAKE_BUILD_TYPE} STREQUAL "Debug") set(DEBUG_BUILD TRUE) set(HAS_DEBUG_INFO TRUE) elseif(${CMAKE_BUILD_TYPE} STREQUAL "RelWithDebInfo") - set(HAS_DEBUG_INFO TRUE) + set(HAS_DEBUG_INFO TRUE) endif() set(ASAN_AVAILABLE FALSE) From f0cc0b9ad5bd0958870c164e7cdd7f5d1f1bd524 Mon Sep 17 00:00:00 2001 From: Alexander Sklar Date: Tue, 18 Apr 2023 11:56:38 -0700 Subject: [PATCH 19/21] pr feedback --- include/wil/cppwinrt_authoring.h | 76 +++++++++++++++++++------------- tests/CppWinRTAuthoringTests.cpp | 39 +++++++++++----- 2 files changed, 73 insertions(+), 42 deletions(-) diff --git a/include/wil/cppwinrt_authoring.h b/include/wil/cppwinrt_authoring.h index ecf97f0c..7ad76671 100644 --- a/include/wil/cppwinrt_authoring.h +++ b/include/wil/cppwinrt_authoring.h @@ -32,42 +32,50 @@ namespace wil } template - struct single_threaded_ro_property : std::conditional_t || std::is_final_v, wil::details::single_threaded_property_storage, T> + struct single_threaded_property : std::conditional_t || std::is_final_v, wil::details::single_threaded_property_storage, T> { - single_threaded_ro_property() = default; - single_threaded_ro_property(T value) : base_type(std::move(value)) { } - + single_threaded_property() = default; + single_threaded_property(T value) : base_type(std::move(value)) {} + + using base_type = std::conditional_t || std::is_final_v, wil::details::single_threaded_property_storage, T>; + const auto& operator()() { return *this; } - template auto operator=(Q&& q) = delete; - private: - using base_type = std::conditional_t, wil::details::single_threaded_property_storage, T>; + + template auto& operator()(Q&& q) + { + *this = std::forward(q); + return *this; + } + + template auto& operator=(Q&& q) + { + static_cast(*this) = std::forward(q); + return *this; + } }; template - struct single_threaded_rw_property : single_threaded_ro_property + struct single_threaded_rw_property : single_threaded_property { - template - single_threaded_rw_property(TArgs&&... args) : single_threaded_ro_property(std::forward(args)...) { } + using base_type = single_threaded_property; + template single_threaded_rw_property(TArgs... value) : base_type(std::move(value)...) {} - using single_threaded_ro_property::operator(); + using base_type::operator(); - auto& operator()() - { - return *this; - } + // needed in lieu of deducing-this template auto& operator()(Q&& q) { - *this = std::forward(q); - return *this; + return *this = std::forward(q); } - - template auto operator=(Q&& q) + // needed in lieu of deducing-this + template auto& operator=(Q&& q) { - return static_cast(*this) = std::forward(q); + base_type::operator=(std::forward(q)); + return *this; } }; @@ -90,7 +98,8 @@ namespace wil } template - auto invoke(TArgs&&... args) { + auto invoke(TArgs&&... args) + { return m_handler(std::forward(args)...); } private: @@ -182,7 +191,8 @@ namespace wil * } * @endcode */ - auto RaisePropertyChanged(std::wstring_view name) { + auto RaisePropertyChanged(std::wstring_view name) + { return m_propertyChanged(self(), Xaml_Data_PropertyChangedEventHandler{ name }); } protected: @@ -200,25 +210,31 @@ namespace wil struct single_threaded_notifying_property : single_threaded_rw_property { using Type = T; + using base_type = single_threaded_rw_property; + using base_type::operator(); - using single_threaded_rw_property::operator(); + template auto& operator()(Q&& q) + { + return *this = std::forward(q); + } - void operator()(const T& value) + template auto& operator=(Q&& q) { - if (value != this->m_value) + if (q != this->operator()()) { - single_threaded_rw_property::operator()(value); - if (m_npc) + static_cast(*this) = std::forward(q); + if (auto strong = m_sender.get(); (m_npc != nullptr) && (strong != nullptr)) { - (*m_npc)(m_sender, Xaml_Data_PropertyChangedEventArgs{ m_name }); + (*m_npc)(strong, Xaml_Data_PropertyChangedEventArgs{ m_name }); } } + return *this; } template single_threaded_notifying_property( winrt::event* npc, - winrt::Windows::Foundation::IInspectable sender, + const winrt::Windows::Foundation::IInspectable& sender, std::wstring_view name, TArgs&&... args) : single_threaded_rw_property(std::forward(args)...), @@ -233,7 +249,7 @@ namespace wil private: std::wstring_view m_name; winrt::event* m_npc; - winrt::Windows::Foundation::IInspectable m_sender; + winrt::weak_ref m_sender; }; /** diff --git a/tests/CppWinRTAuthoringTests.cpp b/tests/CppWinRTAuthoringTests.cpp index 8a196fe8..96c2c984 100644 --- a/tests/CppWinRTAuthoringTests.cpp +++ b/tests/CppWinRTAuthoringTests.cpp @@ -11,22 +11,22 @@ #include #include -TEST_CASE("CppWinRTAuthoringTests::ReadOnly", "[property]") +TEST_CASE("CppWinRTAuthoringTests::Read", "[property]") { int value = 42; - wil::single_threaded_ro_property prop(value); + wil::single_threaded_property prop(value); REQUIRE(prop == value); REQUIRE(prop() == value); REQUIRE(prop == prop()); REQUIRE(prop == prop); - wil::single_threaded_ro_property prop2 = prop; + wil::single_threaded_property prop2 = prop; REQUIRE(prop2 == value); REQUIRE(prop2() == value); REQUIRE(prop2 == prop()); REQUIRE(prop2 == prop); - wil::single_threaded_ro_property prop3; + wil::single_threaded_property prop3; REQUIRE(prop3.empty()); } @@ -67,13 +67,13 @@ TEST_CASE("CppWinRTAuthoringTests::ReadWrite", "[property]") TEST_CASE("CppWinRTAuthoringTests::ReadWriteFromReadOnly", "[property]") { int value = 42; - wil::single_threaded_ro_property prop(value); + wil::single_threaded_property prop(value); REQUIRE(prop == value); REQUIRE(prop() == value); REQUIRE(prop == prop()); REQUIRE(prop == prop); - wil::single_threaded_rw_property prop2 = prop; + wil::single_threaded_rw_property prop2(prop); REQUIRE(prop2 == value); REQUIRE(prop2() == value); REQUIRE(prop2 == prop()); @@ -85,22 +85,35 @@ TEST_CASE("CppWinRTAuthoringTests::ReadWriteFromReadOnly", "[property]") REQUIRE(prop2() == value2); REQUIRE(prop2 == prop2()); REQUIRE(prop2 == prop2); + + wil::single_threaded_rw_property prop3{ prop }; + REQUIRE(prop3 == value); + REQUIRE(prop3() == value); + REQUIRE(prop3 == prop()); + REQUIRE(prop3 == prop); + + wil::single_threaded_rw_property prop4 = prop; + REQUIRE(prop4 == value); + REQUIRE(prop4() == value); + REQUIRE(prop4 == prop()); + REQUIRE(prop4 == prop); } TEST_CASE("CppWinRTAuthoringTests::InStruct", "[property]") { struct TestStruct { - wil::single_threaded_ro_property Prop1{ 42 }; + wil::single_threaded_property Prop1{ 42 }; wil::single_threaded_rw_property Prop2{ 1 }; - wil::single_threaded_ro_property Prop3{ 44 }; + wil::single_threaded_property Prop3{ 44 }; + void foo() + { + Prop1 = -42; + } }; TestStruct test; - static_assert(!std::is_assignable_v, int>, "cannot assign to a readonly property"); - test.Prop2 = 43; - static_assert(!std::is_assignable_v, "cannot assign to a readonly property"); REQUIRE(test.Prop1 == 42); REQUIRE(test.Prop2 == 43); @@ -157,14 +170,16 @@ TEST_CASE("CppWinRTAuthoringTests::NotifyPropertyChanged", "[property][LocalOnly }; auto test = winrt::make(); auto testImpl = winrt::get_self(test); - + bool notified = false; auto token = test.PropertyChanged([&](winrt::Windows::Foundation::IInspectable, winrt::Windows::UI::Xaml::Data::PropertyChangedEventArgs args) { REQUIRE(args.PropertyName() == L"MyProperty"); REQUIRE(testImpl->MyProperty() == 43); + notified = true; }); testImpl->MyProperty(43); + REQUIRE(notified); test.PropertyChanged(token); REQUIRE(testImpl->MyProperty.Name() == L"MyProperty"); manager.Close(); From f99cf6b65f77947c4f925d2939cf89b3dc411a1c Mon Sep 17 00:00:00 2001 From: Alexander Sklar Date: Tue, 18 Apr 2023 14:28:15 -0700 Subject: [PATCH 20/21] add macro alterntive for notifying properties, cleanup --- include/wil/cppwinrt_authoring.h | 37 ++++++++++++++++----- tests/CppWinRTAuthoringTests.cpp | 56 ++++++++++++++++++++++---------- 2 files changed, 67 insertions(+), 26 deletions(-) diff --git a/include/wil/cppwinrt_authoring.h b/include/wil/cppwinrt_authoring.h index 7ad76671..24f6b962 100644 --- a/include/wil/cppwinrt_authoring.h +++ b/include/wil/cppwinrt_authoring.h @@ -147,15 +147,18 @@ namespace wil * @tparam T CRTP type * @details When you declare your class, make this class a base class and pass your class as a template parameter: * @code - * struct MyPage : MyPageT, wil::notify_property_changed_base { - * wil::single_threaded_notifying_property MyInt; - * - * MyPage() : INIT_PROPERTY(MyInt, 42) { } + * struct MyPage : MyPageT, wil::notify_property_changed_base + * { + * wil::single_threaded_notifying_property MyInt; + * MyPage() : INIT_NOTIFYING_PROPERTY(MyInt, 42) { } + * // or + * WIL_NOTIFYING_PROPERTY(int, MyInt, 42); * }; * @endcode */ template + typename Xaml_Data_PropertyChangedEventHandler = wil::details::Xaml_Data_PropertyChangedEventHandler, + typename Xaml_Data_PropertyChangedEventArgs = wil::details::Xaml_Data_PropertyChangedEventArgs> struct notify_property_changed_base { using Type = T; @@ -193,7 +196,7 @@ namespace wil */ auto RaisePropertyChanged(std::wstring_view name) { - return m_propertyChanged(self(), Xaml_Data_PropertyChangedEventHandler{ name }); + return m_propertyChanged(self(), Xaml_Data_PropertyChangedEventArgs{ name }); } protected: winrt::event m_propertyChanged; @@ -253,10 +256,28 @@ namespace wil }; /** - * @def INIT_NOTIFY_PROPERTY + * @def WIL_NOTIFYING_PROPERTY + * @brief use this to stamp out a property that calls RaisePropertyChanged upon changing its value. This is a zero-storage alternative to wil::single_threaded_notifying_property + * @details You can pass an initializer list for the initial property value in the variadic arguments to this macro. + */ +#define WIL_NOTIFYING_PROPERTY(type, name, ...) \ + type m_##name{__VA_ARGS__}; \ + auto name() const noexcept { return m_##name; } \ + auto& name(type value) \ + { \ + if (m_##name != value) \ + { \ + m_##name = std::move(value); \ + RaisePropertyChanged(L#name); \ + } \ + return *this; \ + } \ + + /** + * @def INIT_NOTIFYING_PROPERTY * @brief use this to initialize a wil::single_threaded_notifying_property in your class constructor. */ -#define INIT_NOTIFY_PROPERTY(NAME, VALUE) \ +#define INIT_NOTIFYING_PROPERTY(NAME, VALUE) \ NAME(&m_propertyChanged, *this, L#NAME, VALUE) #endif // !defined(__WIL_CPPWINRT_AUTHORING_INCLUDED_XAML_DATA) && (defined(WINRT_Microsoft_UI_Xaml_Data_H) || defined(WINRT_Windows_UI_Xaml_Data_H)) diff --git a/tests/CppWinRTAuthoringTests.cpp b/tests/CppWinRTAuthoringTests.cpp index 96c2c984..d46ac72b 100644 --- a/tests/CppWinRTAuthoringTests.cpp +++ b/tests/CppWinRTAuthoringTests.cpp @@ -162,26 +162,46 @@ TEST_CASE("CppWinRTAuthoringTests::NotifyPropertyChanged", "[property][LocalOnly auto uninit = wil::RoInitialize_failfast(RO_INIT_SINGLETHREADED); // We need to initialize the XAML core in order to instantiate a PropertyChangedEventArgs. auto manager = winrt::Windows::UI::Xaml::Hosting::WindowsXamlManager::InitializeForCurrentThread(); - - struct Test : winrt::implements, wil::notify_property_changed_base { - wil::single_threaded_notifying_property MyProperty; - Test() : INIT_NOTIFY_PROPERTY(MyProperty, 42) {} - }; - auto test = winrt::make(); - auto testImpl = winrt::get_self(test); - bool notified = false; - auto token = test.PropertyChanged([&](winrt::Windows::Foundation::IInspectable, winrt::Windows::UI::Xaml::Data::PropertyChangedEventArgs args) + struct Test : winrt::implements, wil::notify_property_changed_base + { + wil::single_threaded_notifying_property MyProperty; + Test() : INIT_NOTIFYING_PROPERTY(MyProperty, 42) {} + }; + auto test = winrt::make(); + auto testImpl = winrt::get_self(test); + bool notified = false; + auto token = test.PropertyChanged([&](winrt::Windows::Foundation::IInspectable, winrt::Windows::UI::Xaml::Data::PropertyChangedEventArgs args) + { + REQUIRE(args.PropertyName() == L"MyProperty"); + REQUIRE(testImpl->MyProperty() == 43); + notified = true; + }); + + testImpl->MyProperty(43); + REQUIRE(notified); + test.PropertyChanged(token); + REQUIRE(testImpl->MyProperty.Name() == L"MyProperty"); + } { - REQUIRE(args.PropertyName() == L"MyProperty"); - REQUIRE(testImpl->MyProperty() == 43); - notified = true; - }); - - testImpl->MyProperty(43); - REQUIRE(notified); - test.PropertyChanged(token); - REQUIRE(testImpl->MyProperty.Name() == L"MyProperty"); + struct Test : winrt::implements, wil::notify_property_changed_base + { + WIL_NOTIFYING_PROPERTY(int, MyProperty, 42); + }; + auto test = winrt::make(); + auto testImpl = winrt::get_self(test); + bool notified = false; + auto token = test.PropertyChanged([&](winrt::Windows::Foundation::IInspectable, winrt::Windows::UI::Xaml::Data::PropertyChangedEventArgs args) + { + REQUIRE(args.PropertyName() == L"MyProperty"); + REQUIRE(testImpl->MyProperty() == 43); + notified = true; + }); + + testImpl->MyProperty(43); + REQUIRE(notified); + test.PropertyChanged(token); + } manager.Close(); #endif } From 7c4eae0d83275db14268403ae3691bf51da95932 Mon Sep 17 00:00:00 2001 From: Alexander Sklar Date: Thu, 20 Apr 2023 16:46:26 -0700 Subject: [PATCH 21/21] pr fb --- include/wil/cppwinrt_authoring.h | 10 +++++----- tests/CppWinRTAuthoringTests.cpp | 4 +--- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/include/wil/cppwinrt_authoring.h b/include/wil/cppwinrt_authoring.h index 24f6b962..b80aad06 100644 --- a/include/wil/cppwinrt_authoring.h +++ b/include/wil/cppwinrt_authoring.h @@ -35,7 +35,7 @@ namespace wil struct single_threaded_property : std::conditional_t || std::is_final_v, wil::details::single_threaded_property_storage, T> { single_threaded_property() = default; - single_threaded_property(T value) : base_type(std::move(value)) {} + template single_threaded_property(TArgs&&... value) : base_type(std::forward(value)...) {} using base_type = std::conditional_t || std::is_final_v, wil::details::single_threaded_property_storage, T>; @@ -61,7 +61,7 @@ namespace wil struct single_threaded_rw_property : single_threaded_property { using base_type = single_threaded_property; - template single_threaded_rw_property(TArgs... value) : base_type(std::move(value)...) {} + template single_threaded_rw_property(TArgs&&... value) : base_type(std::forward(value)...) {} using base_type::operator(); @@ -261,14 +261,14 @@ namespace wil * @details You can pass an initializer list for the initial property value in the variadic arguments to this macro. */ #define WIL_NOTIFYING_PROPERTY(type, name, ...) \ - type m_##name{__VA_ARGS__}; \ + type m_##name{__VA_ARGS__}; \ auto name() const noexcept { return m_##name; } \ auto& name(type value) \ { \ if (m_##name != value) \ { \ m_##name = std::move(value); \ - RaisePropertyChanged(L#name); \ + RaisePropertyChanged(L"" #name); \ } \ return *this; \ } \ @@ -278,7 +278,7 @@ namespace wil * @brief use this to initialize a wil::single_threaded_notifying_property in your class constructor. */ #define INIT_NOTIFYING_PROPERTY(NAME, VALUE) \ - NAME(&m_propertyChanged, *this, L#NAME, VALUE) + NAME(&m_propertyChanged, *this, L"" #NAME, VALUE) #endif // !defined(__WIL_CPPWINRT_AUTHORING_INCLUDED_XAML_DATA) && (defined(WINRT_Microsoft_UI_Xaml_Data_H) || defined(WINRT_Windows_UI_Xaml_Data_H)) } // namespace wil diff --git a/tests/CppWinRTAuthoringTests.cpp b/tests/CppWinRTAuthoringTests.cpp index d46ac72b..d615f3af 100644 --- a/tests/CppWinRTAuthoringTests.cpp +++ b/tests/CppWinRTAuthoringTests.cpp @@ -149,9 +149,7 @@ TEST_CASE("CppWinRTAuthoringTests::Events", "[property]") } #endif // WINRT_Windows_Foundation_H -// if using MSVC since we need the embedded manifest bits to use XAML islands -#if _MSVC_LANG >= 201703L && defined(WINRT_Windows_UI_Xaml_Data_H) - +#if defined(WINRT_Windows_UI_Xaml_Data_H) #include // This test cannot run in the same process as the malloc spies tests in wiTest.cpp