From 83a7f508fd57f874a397039dadfb7f2f691c4907 Mon Sep 17 00:00:00 2001 From: Alexander Sklar Date: Sun, 9 Apr 2023 04:30:51 -0700 Subject: [PATCH 01/18] 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/18] . --- 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/18] . --- 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] > --- 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/18] 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/18] 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/18] 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/18] 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/18] . --- 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/18] . --- 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 1ee5cdf9ca2d34db2ee2bb6e4c49826410414b0c Mon Sep 17 00:00:00 2001 From: Alexander Sklar Date: Mon, 17 Apr 2023 12:20:06 -0700 Subject: [PATCH 18/18] add WIL_NAMEOF_MEMBER --- include/wil/cppwinrt_authoring.h | 45 +++++++++++++++++++++++++++++--- tests/CppWinRTAuthoringTests.cpp | 4 +++ 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/include/wil/cppwinrt_authoring.h b/include/wil/cppwinrt_authoring.h index 63745d5d..ee9de6d4 100644 --- a/include/wil/cppwinrt_authoring.h +++ b/include/wil/cppwinrt_authoring.h @@ -197,9 +197,7 @@ namespace details void operator()(const T& value) { if (value != this->m_value) { single_threaded_rw_property::operator()(value); - if (m_npc) { - (*m_npc)(m_sender, Xaml_Data_PropertyChangedEventArgs{ m_name }); - } + Raise(); } } template @@ -217,6 +215,14 @@ namespace details 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; } + auto& Raise() + { + if (m_npc) + { + (*m_npc)(m_sender, Xaml_Data_PropertyChangedEventArgs{ m_name }); + } + return *this; + } private: std::wstring_view m_name; winrt::event* m_npc; @@ -230,6 +236,39 @@ namespace details #define INIT_NOTIFY_PROPERTY(NAME, VALUE) \ NAME(&m_propertyChanged, *this, L#NAME, VALUE) + namespace details + { + template + inline constexpr bool make_false(T) + { + return false; + } + } + +#if defined(_MSC_VER) +// Gets the name of a member (such as a property) as a constexpr string literal. +// This is commonly needed when checking which property changed after receiving a PropertyChanged event. +// It will enforce at compile time that propertyName is a member of typeName, then return propertyName as a string literal. +#define WIL_NAMEOF_MEMBER(typeName, propertyName) \ + __pragma(warning(push)) \ + __pragma(warning(disable:6237)) \ + std::wstring_view( \ + (false && wil::details::make_false(std::add_pointer_t{nullptr}->propertyName) \ + ? L"" : (L"" #propertyName))) \ + __pragma(warning(pop)) +#elif defined(__clang__) +// Gets the name of a member (such as a property) as a constexpr string literal. +// This is commonly needed when checking which property changed after receiving a PropertyChanged event. +// It will enforce at compile time that propertyName is a member of typeName, then return propertyName as a string literal. +#define WIL_NAMEOF_MEMBER(typeName, propertyName) \ + _Pragma("clang diagnostic push") \ + _Pragma("clang diagnostic ignored \"-Wtautological-constant-out-of-range-compare\"") + std::wstring_view( \ + (false && wil::details::make_false(std::add_pointer_t{nullptr}->propertyName) \ + ? L"" : (L"" #propertyName))) \ + _Pragma("clang diagnostic pop") +#endif + #endif // defined(WINRT_Microsoft_UI_Xaml_Data_H) || defined(WINRT_Windows_UI_Xaml_Data_H) #endif // !defined(__WIL_CPPWINRT_AUTHORING_INCLUDED_XAML_DATA) diff --git a/tests/CppWinRTAuthoringTests.cpp b/tests/CppWinRTAuthoringTests.cpp index 8a196fe8..54295bb1 100644 --- a/tests/CppWinRTAuthoringTests.cpp +++ b/tests/CppWinRTAuthoringTests.cpp @@ -167,6 +167,10 @@ TEST_CASE("CppWinRTAuthoringTests::NotifyPropertyChanged", "[property][LocalOnly testImpl->MyProperty(43); test.PropertyChanged(token); REQUIRE(testImpl->MyProperty.Name() == L"MyProperty"); + + constexpr auto name = WIL_NAMEOF_MEMBER(Test, MyProperty); + static_assert(name == L"MyProperty"); + manager.Close(); #endif }