diff --git a/include/wil/cppwinrt_authoring.h b/include/wil/cppwinrt_authoring.h new file mode 100644 index 00000000..b80aad06 --- /dev/null +++ b/include/wil/cppwinrt_authoring.h @@ -0,0 +1,284 @@ +//********************************************************* +// +// 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. +// +//********************************************************* + +namespace wil +{ +#ifndef __WIL_CPPWINRT_AUTHORING_PROPERTIES_INCLUDED +#define __WIL_CPPWINRT_AUTHORING_PROPERTIES_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_property : std::conditional_t || std::is_final_v, wil::details::single_threaded_property_storage, T> + { + single_threaded_property() = default; + 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>; + + const auto& operator()() + { + return *this; + } + + 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_property + { + using base_type = single_threaded_property; + template single_threaded_rw_property(TArgs&&... value) : base_type(std::forward(value)...) {} + + using base_type::operator(); + + // needed in lieu of deducing-this + template auto& operator()(Q&& q) + { + return *this = std::forward(q); + } + + // needed in lieu of deducing-this + template auto& operator=(Q&& q) + { + base_type::operator=(std::forward(q)); + return *this; + } + }; + +#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 + { + template + struct event_base { + 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(std::forward(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 : 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). + * @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 : wil::details::event_base> {}; + +#endif // !defined(__WIL_CPPWINRT_AUTHORING_INCLUDED_FOUNDATION) && defined(WINRT_Windows_Foundation_H) + +#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 + { +#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; +#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 + } + + /** + * @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::single_threaded_notifying_property MyInt; + * MyPage() : INIT_NOTIFYING_PROPERTY(MyInt, 42) { } + * // or + * WIL_NOTIFYING_PROPERTY(int, MyInt, 42); + * }; + * @endcode + */ + template + struct notify_property_changed_base + { + 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_PropertyChangedEventArgs{ 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 single_threaded_notifying_property : single_threaded_rw_property + { + using Type = T; + using base_type = single_threaded_rw_property; + using base_type::operator(); + + template auto& operator()(Q&& q) + { + return *this = std::forward(q); + } + + template auto& operator=(Q&& q) + { + if (q != this->operator()()) + { + static_cast(*this) = std::forward(q); + if (auto strong = m_sender.get(); (m_npc != nullptr) && (strong != nullptr)) + { + (*m_npc)(strong, Xaml_Data_PropertyChangedEventArgs{ m_name }); + } + } + return *this; + } + + template + single_threaded_notifying_property( + winrt::event* npc, + const 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_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_view m_name; + winrt::event* m_npc; + winrt::weak_ref m_sender; + }; + + /** + * @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_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)) +} // namespace wil diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 5020a6c3..7ed7baa8 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) diff --git a/tests/CppWinRTAuthoringTests.cpp b/tests/CppWinRTAuthoringTests.cpp new file mode 100644 index 00000000..d615f3af --- /dev/null +++ b/tests/CppWinRTAuthoringTests.cpp @@ -0,0 +1,206 @@ +#define WINAPI_PARTITION_DESKTOP 1 // for RO_INIT_SINGLETHREADED +#include "common.h" +#undef GetCurrentTime +// check if at least C++17 +#if _MSVC_LANG >= 201703L +#include +#include +#endif + +#include +#include +#include + +TEST_CASE("CppWinRTAuthoringTests::Read", "[property]") +{ + int value = 42; + wil::single_threaded_property prop(value); + REQUIRE(prop == value); + REQUIRE(prop() == value); + REQUIRE(prop == prop()); + REQUIRE(prop == prop); + + wil::single_threaded_property prop2 = prop; + REQUIRE(prop2 == value); + REQUIRE(prop2() == value); + REQUIRE(prop2 == prop()); + REQUIRE(prop2 == prop); + + wil::single_threaded_property prop3; + REQUIRE(prop3.empty()); +} + +TEST_CASE("CppWinRTAuthoringTests::ReadWrite", "[property]") +{ + int value = 42; + wil::single_threaded_rw_property prop(value); + REQUIRE(prop == value); + REQUIRE(prop() == value); + REQUIRE(prop == prop()); + REQUIRE(prop == prop); + + wil::single_threaded_rw_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); + + 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]") +{ + int value = 42; + 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); + 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); + + 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_property Prop1{ 42 }; + wil::single_threaded_rw_property Prop2{ 1 }; + wil::single_threaded_property Prop3{ 44 }; + void foo() + { + Prop1 = -42; + } + }; + + TestStruct test; + test.Prop2 = 43; + + 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("CppWinRTAuthoringTests::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 defined(WINRT_Windows_UI_Xaml_Data_H) +#include + +// 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. + 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_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"); + } + { + 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 +} +#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/cpplatest/CMakeLists.txt b/tests/cpplatest/CMakeLists.txt index 7ae64d54..f0259787 100644 --- a/tests/cpplatest/CMakeLists.txt +++ b/tests/cpplatest/CMakeLists.txt @@ -19,10 +19,12 @@ target_sources(witest.cpplatest PRIVATE ${COROUTINE_SOURCES} ${CMAKE_CURRENT_SOURCE_DIR}/../CppWinRTTests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../CppWinRT20Tests.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 ${CMAKE_CURRENT_SOURCE_DIR}/../WatcherTests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../WinRTTests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../WinVerifyTrustTest.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/app.manifest ) 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 @@ + + + + + + + + + + + + + + +