diff --git a/README.md b/README.md index 19e1493dc..112eeb2f8 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Don't build C++/WinRT yourself - just download the latest version here: https:// ## Working on the compiler -If you really want to build it yourself, the simplest way to do so is to run the `build_test_all.cmd` script in the root directory. Developers needing to work on the C++/WinRT compiler itself should go through the following steps to arrive at an efficient inner loop: +If you really want to build it yourself, the simplest way to do so is to run the `build_test_all.cmd` script in the root directory. It takes optional positional arguments: `build_test_all.cmd [platform] [configuration] [version] [clean] [compiler]`, e.g. `build_test_all.cmd x64 Debug 999.999.999.999 "" clang`. The `compiler` argument defaults to `msvc`; pass `clang` (or `clang-cl`) to build the compiler and test projects with the clang-cl toolset, matching the clang-cl leg of CI. Developers needing to work on the C++/WinRT compiler itself should go through the following steps to arrive at an efficient inner loop: * Open a dev command prompt pointing at the root of the repo. * Open the `cppwinrt.sln` solution. diff --git a/build_test_all.cmd b/build_test_all.cmd index c9beb852c..4ae1ee5ac 100644 --- a/build_test_all.cmd +++ b/build_test_all.cmd @@ -4,38 +4,44 @@ set target_platform=%1 set target_configuration=%2 set target_version=%3 set clean_intermediate_files=%4 +set target_compiler=%5 if "%target_platform%"=="" set target_platform=x64 if "%target_configuration%"=="" set target_configuration=Release if "%target_version%"=="" set target_version=999.999.999.999 +if "%target_compiler%"=="" set target_compiler=msvc + +set compiler_props= +if /i "%target_compiler%"=="clang" set compiler_props=,Clang=1,PlatformToolset=ClangCl +if /i "%target_compiler%"=="clang-cl" set compiler_props=,Clang=1,PlatformToolset=ClangCl if not exist ".\.nuget" mkdir ".\.nuget" if not exist ".\.nuget\nuget.exe" powershell -Command "$ProgressPreference = 'SilentlyContinue' ; Invoke-WebRequest https://dist.nuget.org/win-x86-commandline/latest/nuget.exe -OutFile .\.nuget\nuget.exe" -call .nuget\nuget.exe restore cppwinrt.sln" +call .nuget\nuget.exe restore cppwinrt.sln call .nuget\nuget.exe restore natvis\cppwinrtvisualizer.sln call .nuget\nuget.exe restore test\nuget\NugetTest.sln -call msbuild /m /p:Configuration=%target_configuration%,Platform=%target_platform%,CppWinRTBuildVersion=%target_version% cppwinrt.sln /t:fast_fwd +call msbuild /m /p:Configuration=%target_configuration%,Platform=%target_platform%,CppWinRTBuildVersion=%target_version%%compiler_props% cppwinrt.sln /t:fast_fwd call msbuild /p:Configuration=%target_configuration%,Platform=%target_platform%,Deployment=Component;CppWinRTBuildVersion=%target_version% natvis\cppwinrtvisualizer.sln call msbuild /p:Configuration=%target_configuration%,Platform=%target_platform%,Deployment=Standalone;CppWinRTBuildVersion=%target_version% natvis\cppwinrtvisualizer.sln if "%target_platform%"=="arm64" goto :eof -call msbuild /m /p:Configuration=%target_configuration%,Platform=%target_platform%,CppWinRTBuildVersion=%target_version% cppwinrt.sln /t:cppwinrt +call msbuild /m /p:Configuration=%target_configuration%,Platform=%target_platform%,CppWinRTBuildVersion=%target_version%%compiler_props% cppwinrt.sln /t:cppwinrt call msbuild /p:Configuration=%target_configuration%,Platform=%target_platform%,CppWinRTBuildVersion=%target_version% test\nuget\NugetTest.sln -call msbuild /m /p:Configuration=%target_configuration%,Platform=%target_platform%,CppWinRTBuildVersion=%target_version% cppwinrt.sln /t:test\test -call msbuild /m /p:Configuration=%target_configuration%,Platform=%target_platform%,CppWinRTBuildVersion=%target_version% cppwinrt.sln /t:test\test_nocoro -call msbuild /m /p:Configuration=%target_configuration%,Platform=%target_platform%,CppWinRTBuildVersion=%target_version% cppwinrt.sln /t:test\test_cpp20 -call msbuild /m /p:Configuration=%target_configuration%,Platform=%target_platform%,CppWinRTBuildVersion=%target_version% cppwinrt.sln /t:test\test_cpp20_no_sourcelocation -call msbuild /m /p:Configuration=%target_configuration%,Platform=%target_platform%,CppWinRTBuildVersion=%target_version% cppwinrt.sln /t:test\test_fast -call msbuild /m /p:Configuration=%target_configuration%,Platform=%target_platform%,CppWinRTBuildVersion=%target_version% cppwinrt.sln /t:test\test_slow -call msbuild /m /p:Configuration=%target_configuration%,Platform=%target_platform%,CppWinRTBuildVersion=%target_version% cppwinrt.sln /t:test\test_module_lock_custom -call msbuild /m /p:Configuration=%target_configuration%,Platform=%target_platform%,CppWinRTBuildVersion=%target_version% cppwinrt.sln /t:test\test_module_lock_none -call msbuild /m /p:Configuration=%target_configuration%,Platform=%target_platform%,CppWinRTBuildVersion=%target_version% cppwinrt.sln /t:test\test_module_lock_none -call msbuild /m /p:Configuration=%target_configuration%,Platform=%target_platform%,CppWinRTBuildVersion=%target_version% cppwinrt.sln /t:test\old_tests\test_old +call msbuild /m /p:Configuration=%target_configuration%,Platform=%target_platform%,CppWinRTBuildVersion=%target_version%%compiler_props% cppwinrt.sln /t:test\test +call msbuild /m /p:Configuration=%target_configuration%,Platform=%target_platform%,CppWinRTBuildVersion=%target_version%%compiler_props% cppwinrt.sln /t:test\test_nocoro +call msbuild /m /p:Configuration=%target_configuration%,Platform=%target_platform%,CppWinRTBuildVersion=%target_version%%compiler_props% cppwinrt.sln /t:test\test_cpp20 +call msbuild /m /p:Configuration=%target_configuration%,Platform=%target_platform%,CppWinRTBuildVersion=%target_version%%compiler_props% cppwinrt.sln /t:test\test_cpp20_no_sourcelocation +call msbuild /m /p:Configuration=%target_configuration%,Platform=%target_platform%,CppWinRTBuildVersion=%target_version%%compiler_props% cppwinrt.sln /t:test\test_fast +call msbuild /m /p:Configuration=%target_configuration%,Platform=%target_platform%,CppWinRTBuildVersion=%target_version%%compiler_props% cppwinrt.sln /t:test\test_slow +call msbuild /m /p:Configuration=%target_configuration%,Platform=%target_platform%,CppWinRTBuildVersion=%target_version%%compiler_props% cppwinrt.sln /t:test\test_module_lock_custom +call msbuild /m /p:Configuration=%target_configuration%,Platform=%target_platform%,CppWinRTBuildVersion=%target_version%%compiler_props% cppwinrt.sln /t:test\test_module_lock_none +call msbuild /m /p:Configuration=%target_configuration%,Platform=%target_platform%,CppWinRTBuildVersion=%target_version%%compiler_props% cppwinrt.sln /t:test\test_module_lock_none +call msbuild /m /p:Configuration=%target_configuration%,Platform=%target_platform%,CppWinRTBuildVersion=%target_version%%compiler_props% cppwinrt.sln /t:test\old_tests\test_old call run_tests.cmd %target_platform% %target_configuration% diff --git a/strings/base_coroutine_foundation.h b/strings/base_coroutine_foundation.h index 5cefad836..051e0aa87 100644 --- a/strings/base_coroutine_foundation.h +++ b/strings/base_coroutine_foundation.h @@ -837,6 +837,86 @@ WINRT_IMPL_STD_EXPORT namespace std }; } +WINRT_EXPORT namespace winrt::impl +{ + template + struct ready_async_base : implements + { + using AsyncStatus = Windows::Foundation::AsyncStatus; + + void Completed(async_completed_handler_t const& handler) + { + if (m_completed_assigned.exchange(true)) + { + throw hresult_illegal_delegate_assignment(); + } + + if (handler) + { + winrt::impl::invoke(handler, *static_cast(this), AsyncStatus::Completed); + } + } + + auto Completed() noexcept + { + return async_completed_handler_t{ nullptr }; + } + + std::uint32_t Id() const noexcept + { + return 1; + } + + AsyncStatus Status() const noexcept + { + return AsyncStatus::Completed; + } + + hresult ErrorCode() const noexcept + { + return 0; + } + + void Cancel() const noexcept + { + } + + void Close() const noexcept + { + } + + private: + + std::atomic m_completed_assigned{ false }; + }; + + template + struct ready_async_operation : + ready_async_base, Windows::Foundation::IAsyncOperation> + { + explicit ready_async_operation(TResult value) : m_result(std::move(value)) + { + } + + TResult GetResults() + { + return m_result; + } + + private: + + TResult m_result; + }; + + struct ready_async_action : + ready_async_base + { + void GetResults() const noexcept + { + } + }; +} + WINRT_EXPORT namespace winrt { template @@ -884,5 +964,16 @@ WINRT_EXPORT namespace winrt impl::check_status_canceled(shared->status); co_return shared->result.GetResults(); } + + template + Windows::Foundation::IAsyncOperation> make_ready(TResult&& value) + { + return make>>(std::forward(value)); + } + + inline Windows::Foundation::IAsyncAction make_ready() noexcept + { + return make(); + } } #endif diff --git a/strings/base_iterator.h b/strings/base_iterator.h index 46c0c6b63..afb45aaff 100644 --- a/strings/base_iterator.h +++ b/strings/base_iterator.h @@ -1,6 +1,35 @@ WINRT_EXPORT namespace winrt::impl { + // Detects whether an indexed (GetAt-capable) collection also exposes the bulk + // GetMany(startIndex, array) accessor. IBindableVectorView and similar have GetAt but no GetMany. + template + class has_GetMany_indexed + { + template ().GetAt(0)), + typename = decltype(std::declval().GetMany(0u, std::declval&>()))> + static constexpr bool get_value(int) { return true; } + template static constexpr bool get_value(...) { return false; } + + public: + + static constexpr bool value = get_value(0); + }; + + // Detects whether an iterator exposes the bulk GetMany(array) accessor. IBindableIterator does not. + template + class has_GetMany_iter + { + template ().Current()), + typename = decltype(std::declval().GetMany(std::declval&>()))> + static constexpr bool get_value(int) { return true; } + template static constexpr bool get_value(...) { return false; } + + public: + + static constexpr bool value = get_value(0); + }; + template struct fast_iterator { @@ -73,12 +102,12 @@ WINRT_EXPORT namespace winrt::impl reference operator*() const { - return m_collection->GetAt(m_index); + return fetch(m_index); } reference operator[](difference_type n) const { - return m_collection->GetAt(m_index + static_cast(n)); + return fetch(m_index + static_cast(n)); } bool operator==(fast_iterator const& other) const noexcept @@ -126,8 +155,46 @@ WINRT_EXPORT namespace winrt::impl private: + // Batched forward traversal for random-access (GetAt-capable) collections that also expose + // GetMany. Instead of one GetAt ABI call per element, `fetch` fills a small block with a single + // GetMany call and serves in-window reads from it, so range-for (sequential ++ then *) crosses + // the ABI ~once per `block` elements. Random access still works: an out-of-window index + // re-anchors the block there, and an at/after-end index defers to GetAt so the component's + // bounds behavior (E_BOUNDS) is preserved. Elements are copied out -- no move-out -- so a given + // index may be read repeatedly, as the random-access contract requires. Collections without + // GetMany, or whose element type is not default-constructible, fall back to plain GetAt. + static constexpr bool can_batch = std::is_default_constructible_v && has_GetMany_indexed::value; + + static constexpr std::uint32_t block = static_cast( + (std::min)(128, (std::max)(1, std::size_t{ 2048 } / sizeof(value_type)))); + + struct no_buffer {}; + using buffer_type = std::conditional_t, no_buffer>; + + reference fetch(std::uint32_t const index) const + { + if constexpr (can_batch) + { + if (index < m_buffer_base || index >= m_buffer_base + m_buffer_size) + { + m_buffer_base = index; + m_buffer_size = m_collection->GetMany(index, m_buffer); + } + + if (index < m_buffer_base + m_buffer_size) + { + return m_buffer[index - m_buffer_base]; + } + } + + return m_collection->GetAt(index); + } + T const* m_collection = nullptr; std::uint32_t m_index = 0; + mutable std::uint32_t m_buffer_base = 0; + mutable std::uint32_t m_buffer_size = 0; + mutable buffer_type m_buffer{}; }; template @@ -141,23 +208,115 @@ WINRT_EXPORT namespace winrt::impl static constexpr bool value = get_value(0); }; - template ::value, int> = 0> - auto get_begin_iterator(T const& collection) -> decltype(collection.First()) + // Forward iterator that batches an IIterator via GetMany into a small buffer and yields from + // it, so range-for over a collection that lacks GetAt crosses the ABI once per block instead of + // once per element (Current/MoveNext). Single-pass, matching IIterator's semantics. Only used + // when the iterator exposes GetMany and its element type is default-constructible. + template + struct buffered_iterator { - auto result = collection.First(); + using value_type = decltype(std::declval().Current()); + using iterator_category = std::input_iterator_tag; + using difference_type = std::ptrdiff_t; + using pointer = value_type const*; + using reference = value_type const&; + + static constexpr bool can_batch = std::is_default_constructible_v && has_GetMany_iter::value; + + static constexpr std::uint32_t buffer_capacity = static_cast( + (std::min)(128, (std::max)(1, std::size_t{ 2048 } / sizeof(value_type)))); + + buffered_iterator() noexcept = default; + + explicit buffered_iterator(Iterator iterator) : m_iterator(std::move(iterator)) + { + fill(); + } + + reference operator*() const noexcept + { + return m_buffer[m_index]; + } + + pointer operator->() const noexcept + { + return std::addressof(m_buffer[m_index]); + } + + buffered_iterator& operator++() + { + if (++m_index == m_size) + { + fill(); + } + + return *this; + } + + buffered_iterator operator++(int) + { + auto previous = *this; + ++*this; + return previous; + } - if (!result.HasCurrent()) + bool operator==(buffered_iterator const& other) const noexcept { - return {}; + return (m_size == 0) && (other.m_size == 0); } - return result; + bool operator!=(buffered_iterator const& other) const noexcept + { + return !(*this == other); + } + + private: + + void fill() + { + m_index = 0; + m_size = m_iterator ? m_iterator.GetMany(m_buffer) : 0; + } + + Iterator m_iterator{ nullptr }; + std::array m_buffer; + std::uint32_t m_size{ 0 }; + std::uint32_t m_index{ 0 }; + }; + + template ::value, int> = 0> + auto get_begin_iterator(T const& collection) + { + using iterator_type = decltype(collection.First()); + if constexpr (buffered_iterator::can_batch) + { + return buffered_iterator{ collection.First() }; + } + else + { + auto result = collection.First(); + + if (!result.HasCurrent()) + { + return iterator_type{}; + } + + return result; + } } template ::value, int> = 0> - auto get_end_iterator([[maybe_unused]] T const& collection) noexcept -> decltype(collection.First()) + auto get_end_iterator([[maybe_unused]] T const& collection) { - return {}; + using iterator_type = decltype(collection.First()); + if constexpr (buffered_iterator::can_batch) + { + return buffered_iterator{}; + } + else + { + return iterator_type{}; + } } template ::value, int> = 0> diff --git a/strings/base_reference_produce.h b/strings/base_reference_produce.h index abffb3384..c925fa96d 100644 --- a/strings/base_reference_produce.h +++ b/strings/base_reference_produce.h @@ -2,7 +2,33 @@ WINRT_EXPORT namespace winrt::impl { template - struct reference : implements, Windows::Foundation::IReference, Windows::Foundation::IPropertyValue> + struct reference; + + // The scalar types that combase PropertyValue can carry by value. box_value on one of these + // produces an in-process reference for the fast path, but must still marshal by value across + // apartments/processes so the destination sees a real PropertyValue copy rather than a proxy. + template + inline constexpr bool is_stock_reference_v = + std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v; + + // Stock scalar references are marked non_agile so that our query_interface_tearoff supplies + // IMarshal (delegating to combase PropertyValue for by-value marshaling) instead of the default + // free-threaded-marshaler that marshals by reference. All other T keep the default agile shape. + template + using reference_base_t = std::conditional_t< + is_stock_reference_v, + implements, Windows::Foundation::IReference, Windows::Foundation::IPropertyValue, non_agile>, + implements, Windows::Foundation::IReference, Windows::Foundation::IPropertyValue>>; + + template + struct reference : reference_base_t { reference(T const& value) : m_value(value) { @@ -15,12 +41,30 @@ WINRT_EXPORT namespace winrt::impl Windows::Foundation::PropertyType Type() const noexcept { - return Windows::Foundation::PropertyType::OtherType; + using pt = Windows::Foundation::PropertyType; + + if constexpr (std::is_same_v) { return pt::UInt8; } + else if constexpr (std::is_same_v) { return pt::Int16; } + else if constexpr (std::is_same_v) { return pt::UInt16; } + else if constexpr (std::is_same_v) { return pt::Int32; } + else if constexpr (std::is_same_v) { return pt::UInt32; } + else if constexpr (std::is_same_v) { return pt::Int64; } + else if constexpr (std::is_same_v) { return pt::UInt64; } + else if constexpr (std::is_same_v) { return pt::Single; } + else if constexpr (std::is_same_v) { return pt::Double; } + else if constexpr (std::is_same_v) { return pt::Char16; } + else if constexpr (std::is_same_v) { return pt::Boolean; } + else if constexpr (std::is_same_v) { return pt::String; } + else if constexpr (std::is_same_v) { return pt::Guid; } + else if constexpr (std::is_same_v) { return pt::DateTime; } + else if constexpr (std::is_same_v) { return pt::TimeSpan; } + else if constexpr (std::is_same_v) { return pt::Point; } + else { return pt::OtherType; } } static constexpr bool IsNumericScalar() noexcept { - return std::is_arithmetic_v || std::is_enum_v; + return (std::is_arithmetic_v && !std::is_same_v && !std::is_same_v) || std::is_enum_v; } std::uint8_t GetUInt8() const @@ -58,15 +102,15 @@ WINRT_EXPORT namespace winrt::impl return to_scalar(); } - float GetSingle() { throw hresult_not_implemented(); } - double GetDouble() { throw hresult_not_implemented(); } - char16_t GetChar16() { throw hresult_not_implemented(); } - bool GetBoolean() { throw hresult_not_implemented(); } - hstring GetString() { throw hresult_not_implemented(); } - guid GetGuid() { throw hresult_not_implemented(); } - Windows::Foundation::DateTime GetDateTime() { throw hresult_not_implemented(); } - Windows::Foundation::TimeSpan GetTimeSpan() { throw hresult_not_implemented(); } - Windows::Foundation::Point GetPoint() { throw hresult_not_implemented(); } + float GetSingle() { return to_scalar(); } + double GetDouble() { return to_scalar(); } + char16_t GetChar16() { if constexpr (std::is_same_v) { return m_value; } else { throw hresult_not_implemented(); } } + bool GetBoolean() { if constexpr (std::is_same_v) { return m_value; } else { throw hresult_not_implemented(); } } + hstring GetString() { if constexpr (std::is_same_v) { return m_value; } else { throw hresult_not_implemented(); } } + guid GetGuid() { if constexpr (std::is_same_v) { return m_value; } else { throw hresult_not_implemented(); } } + Windows::Foundation::DateTime GetDateTime() { if constexpr (std::is_same_v) { return m_value; } else { throw hresult_not_implemented(); } } + Windows::Foundation::TimeSpan GetTimeSpan() { if constexpr (std::is_same_v) { return m_value; } else { throw hresult_not_implemented(); } } + Windows::Foundation::Point GetPoint() { if constexpr (std::is_same_v) { return m_value; } else { throw hresult_not_implemented(); } } Windows::Foundation::Size GetSize() { throw hresult_not_implemented(); } Windows::Foundation::Rect GetRect() { throw hresult_not_implemented(); } void GetUInt8Array(com_array &) { throw hresult_not_implemented(); } @@ -91,6 +135,67 @@ WINRT_EXPORT namespace winrt::impl private: + // For stock scalar T, hand out an IMarshal that marshals by value: build the equivalent + // combase PropertyValue on demand and delegate marshaling to it. This is lazy - box_value and + // unbox_value never touch combase; the hop only happens if the reference is actually marshaled. + std::int32_t query_interface_tearoff(guid const& id, void** object) const noexcept override + { + if constexpr (is_stock_reference_v) + { + if (is_guid_of(id)) + { + try + { + auto marshal = create_property_value().template as(); + *object = detach_abi(marshal); + return error_ok; + } + catch (...) + { + *object = nullptr; + return to_hresult(); + } + } + + // reference is immutable, so it is safe to call from any apartment. Advertise + // IAgileObject (as combase PropertyValue does) so callers keep the agile fast path; + // cross-apartment/process marshaling still routes through the by-value IMarshal above. + if (is_guid_of(id)) + { + auto unknown = reinterpret_cast(to_abi>(this)); + unknown->AddRef(); + *object = unknown; + return error_ok; + } + } + + *object = nullptr; + return error_no_interface; + } + + Windows::Foundation::IInspectable create_property_value() const + { + using pv = Windows::Foundation::PropertyValue; + + if constexpr (std::is_same_v) { return pv::CreateUInt8(m_value); } + else if constexpr (std::is_same_v) { return pv::CreateInt16(m_value); } + else if constexpr (std::is_same_v) { return pv::CreateUInt16(m_value); } + else if constexpr (std::is_same_v) { return pv::CreateInt32(m_value); } + else if constexpr (std::is_same_v) { return pv::CreateUInt32(m_value); } + else if constexpr (std::is_same_v) { return pv::CreateInt64(m_value); } + else if constexpr (std::is_same_v) { return pv::CreateUInt64(m_value); } + else if constexpr (std::is_same_v) { return pv::CreateSingle(m_value); } + else if constexpr (std::is_same_v) { return pv::CreateDouble(m_value); } + else if constexpr (std::is_same_v) { return pv::CreateChar16(m_value); } + else if constexpr (std::is_same_v) { return pv::CreateBoolean(m_value); } + else if constexpr (std::is_same_v) { return pv::CreateString(m_value); } + else if constexpr (std::is_same_v) { return pv::CreateGuid(m_value); } + else if constexpr (std::is_same_v) { return pv::CreateDateTime(m_value); } + else if constexpr (std::is_same_v) { return pv::CreateTimeSpan(m_value); } + else if constexpr (std::is_same_v) { return pv::CreatePoint(m_value); } + else { return nullptr; } + } + template To to_scalar() const { @@ -114,90 +219,6 @@ WINRT_EXPORT namespace winrt::impl using itf = Windows::Foundation::IReference; }; - template <> - struct reference_traits - { - static auto make(std::uint8_t value) { return Windows::Foundation::PropertyValue::CreateUInt8(value); } - using itf = Windows::Foundation::IReference; - }; - - template <> - struct reference_traits - { - static auto make(std::uint16_t value) { return Windows::Foundation::PropertyValue::CreateUInt16(value); } - using itf = Windows::Foundation::IReference; - }; - - template <> - struct reference_traits - { - static auto make(std::int16_t value) { return Windows::Foundation::PropertyValue::CreateInt16(value); } - using itf = Windows::Foundation::IReference; - }; - - template <> - struct reference_traits - { - static auto make(std::uint32_t value) { return Windows::Foundation::PropertyValue::CreateUInt32(value); } - using itf = Windows::Foundation::IReference; - }; - - template <> - struct reference_traits - { - static auto make(std::int32_t value) { return Windows::Foundation::PropertyValue::CreateInt32(value); } - using itf = Windows::Foundation::IReference; - }; - - template <> - struct reference_traits - { - static auto make(std::uint64_t value) { return Windows::Foundation::PropertyValue::CreateUInt64(value); } - using itf = Windows::Foundation::IReference; - }; - - template <> - struct reference_traits - { - static auto make(std::int64_t value) { return Windows::Foundation::PropertyValue::CreateInt64(value); } - using itf = Windows::Foundation::IReference; - }; - - template <> - struct reference_traits - { - static auto make(float value) { return Windows::Foundation::PropertyValue::CreateSingle(value); } - using itf = Windows::Foundation::IReference; - }; - - template <> - struct reference_traits - { - static auto make(double value) { return Windows::Foundation::PropertyValue::CreateDouble(value); } - using itf = Windows::Foundation::IReference; - }; - - template <> - struct reference_traits - { - static auto make(char16_t value) { return Windows::Foundation::PropertyValue::CreateChar16(value); } - using itf = Windows::Foundation::IReference; - }; - - template <> - struct reference_traits - { - static auto make(bool value) { return Windows::Foundation::PropertyValue::CreateBoolean(value); } - using itf = Windows::Foundation::IReference; - }; - - template <> - struct reference_traits - { - static auto make(hstring const& value) { return Windows::Foundation::PropertyValue::CreateString(value); } - using itf = Windows::Foundation::IReference; - }; - template <> struct reference_traits { @@ -205,41 +226,13 @@ WINRT_EXPORT namespace winrt::impl using itf = Windows::Foundation::IInspectable; }; - template <> - struct reference_traits - { - static auto make(guid const& value) { return Windows::Foundation::PropertyValue::CreateGuid(value); } - using itf = Windows::Foundation::IReference; - }; - template <> struct reference_traits { - static auto make(GUID const& value) { return Windows::Foundation::PropertyValue::CreateGuid(reinterpret_cast(value)); } + static auto make(GUID const& value) { return reference_traits::make(reinterpret_cast(value)); } using itf = Windows::Foundation::IReference; }; - template <> - struct reference_traits - { - static auto make(Windows::Foundation::DateTime value) { return Windows::Foundation::PropertyValue::CreateDateTime(value); } - using itf = Windows::Foundation::IReference; - }; - - template <> - struct reference_traits - { - static auto make(Windows::Foundation::TimeSpan value) { return Windows::Foundation::PropertyValue::CreateTimeSpan(value); } - using itf = Windows::Foundation::IReference; - }; - - template <> - struct reference_traits - { - static auto make(Windows::Foundation::Point const& value) { return Windows::Foundation::PropertyValue::CreatePoint(value); } - using itf = Windows::Foundation::IReference; - }; - template <> struct reference_traits { diff --git a/strings/base_string.h b/strings/base_string.h index 6b1fb37b5..d0cfac111 100644 --- a/strings/base_string.h +++ b/strings/base_string.h @@ -155,6 +155,24 @@ WINRT_EXPORT namespace winrt::impl return nullptr; } }; + + template + struct hstring_literal_storage + { + static constexpr std::size_t size = N; + wchar_t value[N]; + + constexpr hstring_literal_storage(wchar_t const (&str)[N]) noexcept + { + for (std::size_t i = 0; i != N; ++i) + { + value[i] = str[i]; + } + } + }; + + template + hstring_literal_storage(wchar_t const (&)[N]) -> hstring_literal_storage; } WINRT_EXPORT namespace winrt @@ -386,6 +404,30 @@ WINRT_EXPORT namespace winrt handle_type m_handle; }; + struct hstring_reference + { + constexpr hstring_reference() noexcept = default; + + constexpr explicit hstring_reference(impl::hstring_header const* header) noexcept : + m_handle(const_cast(header)) + { + } + + operator hstring const&() const noexcept + { + return *reinterpret_cast(this); + } + + private: + + [[maybe_unused]] void* m_handle{}; + }; + + inline void* get_abi(hstring_reference const& object) noexcept + { + return *(void**)(&object); + } + inline void* get_abi(hstring const& object) noexcept { return *(void**)(&object); @@ -437,6 +479,44 @@ WINRT_EXPORT namespace winrt } } +#if defined(__cpp_nontype_template_args) && __cpp_nontype_template_args >= 201911L + +WINRT_EXPORT namespace winrt::impl +{ + // The fast-pass header for each distinct literal, materialized once as a compile-time + // constant so `_hs` is a constant expression rather than per-call work. + template + inline constexpr hstring_header hstring_literal_header + { + hstring_reference_flag, + static_cast(Literal.size - 1), + 0, + 0, + Literal.value + }; +} + +WINRT_EXPORT namespace winrt +{ + inline namespace literals + { + template + constexpr hstring_reference operator ""_hs() noexcept + { + if constexpr (Literal.size <= 1) + { + return hstring_reference{}; + } + else + { + return hstring_reference{ &impl::hstring_literal_header }; + } + } + } +} + +#endif + #ifdef __cpp_lib_format template<> struct std::formatter : std::formatter {}; diff --git a/strings/base_string_input.h b/strings/base_string_input.h index 71cd5f3c0..ccf955547 100644 --- a/strings/base_string_input.h +++ b/strings/base_string_input.h @@ -18,6 +18,10 @@ WINRT_EXPORT namespace winrt::param { } + hstring(winrt::hstring_reference const& value) noexcept : m_handle(get_abi(value)) + { + } + hstring(std::wstring_view const& value) noexcept { create_string_reference(value.data(), value.size()); diff --git a/test/test/buffered_iterator.cpp b/test/test/buffered_iterator.cpp new file mode 100644 index 000000000..49c6814ed --- /dev/null +++ b/test/test/buffered_iterator.cpp @@ -0,0 +1,71 @@ +#include "pch.h" + +using namespace winrt; +using namespace Windows::Foundation::Collections; + +TEST_CASE("buffered_iterator") +{ + // Iterating through IIterable (which has no GetAt) exercises the buffered + // GetMany path. Use enough elements to span multiple buffer blocks so a refill + // is required. + { + std::vector expected; + for (int32_t i = 0; i < 300; ++i) + { + expected.push_back(i); + } + + IIterable iterable = single_threaded_vector(std::vector(expected)); + + std::vector observed; + for (auto&& value : iterable) + { + observed.push_back(value); + } + + REQUIRE(observed == expected); + } + + // Exactly one element beyond a single block boundary (int32_t block is 128). + { + IIterable iterable = single_threaded_vector(std::vector(129, 7)); + + uint32_t count = 0; + for (auto&& value : iterable) + { + REQUIRE(value == 7); + ++count; + } + + REQUIRE(count == 129); + } + + // Empty collection yields nothing. + { + IIterable iterable = single_threaded_vector(); + + uint32_t count = 0; + for (auto&& value : iterable) + { + (void)value; + ++count; + } + + REQUIRE(count == 0); + } + + // A non-trivial element type round-trips through the buffer. + { + IIterable iterable = single_threaded_vector({ L"a", L"b", L"c" }); + + std::vector observed; + for (auto&& value : iterable) + { + observed.push_back(value); + } + + REQUIRE(observed.size() == 3); + REQUIRE(observed[0] == L"a"); + REQUIRE(observed[2] == L"c"); + } +} diff --git a/test/test/make_ready.cpp b/test/test/make_ready.cpp new file mode 100644 index 000000000..37c10080f --- /dev/null +++ b/test/test/make_ready.cpp @@ -0,0 +1,62 @@ +#include "pch.h" + +using namespace winrt; +using namespace Windows::Foundation; + +TEST_CASE("make_ready") +{ + // Completed synchronously with a value, with no coroutine frame. + { + IAsyncOperation op = make_ready(42); + REQUIRE(op.Status() == AsyncStatus::Completed); + REQUIRE(op.ErrorCode() == 0); + REQUIRE(op.GetResults() == 42); + REQUIRE(op.get() == 42); + } + + // co_await yields the value through the synchronous-completion path. + { + auto coro = []() -> IAsyncOperation + { + co_return co_await make_ready(7); + }; + + REQUIRE(coro().get() == 7); + } + + // A Completed handler on an already-completed operation fires immediately. + { + auto op = make_ready(5); + int32_t observed = 0; + AsyncStatus observed_status = AsyncStatus::Started; + + op.Completed([&](IAsyncOperation const& sender, AsyncStatus status) + { + observed = sender.GetResults(); + observed_status = status; + }); + + REQUIRE(observed == 5); + REQUIRE(observed_status == AsyncStatus::Completed); + } + + // Assigning Completed twice is illegal, matching the coroutine promise. + { + auto op = make_ready(1); + op.Completed([](auto&&, auto&&) {}); + REQUIRE_THROWS_AS(op.Completed([](auto&&, auto&&) {}), hresult_illegal_delegate_assignment); + } + + // Action variant carries no result. + { + IAsyncAction action = make_ready(); + REQUIRE(action.Status() == AsyncStatus::Completed); + action.get(); + } + + // A non-trivial result type round-trips. + { + auto op = make_ready(hstring{ L"ready" }); + REQUIRE(op.get() == L"ready"); + } +} diff --git a/test/test/reference_boxing.cpp b/test/test/reference_boxing.cpp new file mode 100644 index 000000000..fbd83c32f --- /dev/null +++ b/test/test/reference_boxing.cpp @@ -0,0 +1,122 @@ +#include "pch.h" +#include +#include + +using namespace winrt; +using namespace Windows::Foundation; + +// Scalar box_value now produces a local IReference/IPropertyValue instead of hopping to +// combase PropertyValue. These confirm it reports the correct PropertyType, keeps combase-style +// numeric conversion on mismatched getters, and round-trips through unbox_value. +TEST_CASE("reference_boxing") +{ + { + auto boxed = box_value(42); + auto pv = boxed.as(); + REQUIRE(pv.Type() == PropertyType::Int32); + REQUIRE(pv.IsNumericScalar()); + REQUIRE(pv.GetInt32() == 42); + REQUIRE(pv.GetInt16() == 42); + REQUIRE(pv.GetDouble() == 42.0); + REQUIRE(unbox_value(boxed) == 42); + } + + { + auto pv = box_value(3.5).as(); + REQUIRE(pv.Type() == PropertyType::Double); + REQUIRE(pv.IsNumericScalar()); + REQUIRE(pv.GetDouble() == 3.5); + REQUIRE(pv.GetSingle() == 3.5f); + } + + { + auto pv = box_value(hstring{ L"hello" }).as(); + REQUIRE(pv.Type() == PropertyType::String); + REQUIRE(!pv.IsNumericScalar()); + REQUIRE(pv.GetString() == L"hello"); + REQUIRE_THROWS_AS(pv.GetInt32(), hresult_not_implemented); + } + + { + auto pv = box_value(true).as(); + REQUIRE(pv.Type() == PropertyType::Boolean); + REQUIRE(!pv.IsNumericScalar()); + REQUIRE(pv.GetBoolean()); + REQUIRE_THROWS_AS(pv.GetInt32(), hresult_not_implemented); + } + + { + guid const g{ 0x11223344, 0x5566, 0x7788, { 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00 } }; + auto pv = box_value(g).as(); + REQUIRE(pv.Type() == PropertyType::Guid); + REQUIRE(!pv.IsNumericScalar()); + REQUIRE(pv.GetGuid() == g); + } + + { + auto pv = box_value(static_cast(7)).as(); + REQUIRE(pv.Type() == PropertyType::UInt8); + REQUIRE(pv.IsNumericScalar()); + REQUIRE(pv.GetUInt8() == 7); + REQUIRE(unbox_value(box_value(static_cast(7))) == 7); + } + + // DateTime, TimeSpan, and Point are also boxed in-process now (they still marshal by value). + { + Point const point{ 3.0f, 4.0f }; + auto pv = box_value(point).as(); + REQUIRE(pv.Type() == PropertyType::Point); + REQUIRE(!pv.IsNumericScalar()); + REQUIRE(pv.GetPoint().X == point.X); + REQUIRE(pv.GetPoint().Y == point.Y); + auto const round_tripped = unbox_value(box_value(point)); + REQUIRE(round_tripped.X == point.X); + REQUIRE(round_tripped.Y == point.Y); + REQUIRE_THROWS_AS(pv.GetInt32(), hresult_not_implemented); + } + + { + TimeSpan const span{ std::chrono::seconds{ 90 } }; + auto pv = box_value(span).as(); + REQUIRE(pv.Type() == PropertyType::TimeSpan); + REQUIRE(!pv.IsNumericScalar()); + REQUIRE(pv.GetTimeSpan() == span); + REQUIRE(unbox_value(box_value(span)) == span); + } + + { + DateTime const when{ TimeSpan{ std::chrono::seconds{ 1000 } } }; + auto pv = box_value(when).as(); + REQUIRE(pv.Type() == PropertyType::DateTime); + REQUIRE(!pv.IsNumericScalar()); + REQUIRE(pv.GetDateTime() == when); + REQUIRE(unbox_value(box_value(when)) == when); + } +} + +// The in-proc reference stays agile but must marshal by value across processes, exactly like a real +// combase PropertyValue. Prove it by confirming our IMarshal reports the SAME unmarshal class as a +// genuine PropertyValue - i.e. we forward marshaling to combase - and specifically NOT the +// free-threaded (marshal-by-reference) class the default agile path would have used. +TEST_CASE("reference_boxing marshal by value") +{ + auto boxed = box_value(42); + REQUIRE(boxed.try_as()); + auto ours = boxed.as(); + + auto genuine = PropertyValue::CreateInt32(42); + auto reference = genuine.as(); + + guid our_clsid{}; + guid reference_clsid{}; + check_hresult(ours->GetUnmarshalClass(guid_of(), get_unknown(boxed), + MSHCTX_DIFFERENTMACHINE, nullptr, MSHLFLAGS_NORMAL, &our_clsid)); + check_hresult(reference->GetUnmarshalClass(guid_of(), get_unknown(genuine), + MSHCTX_DIFFERENTMACHINE, nullptr, MSHLFLAGS_NORMAL, &reference_clsid)); + + REQUIRE(our_clsid == reference_clsid); + + // CLSID_InProcFreeMarshaler - the by-reference class the agile FTM would have produced. + guid const free_threaded_marshaler{ 0x0000033A, 0x0000, 0x0000, { 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46 } }; + REQUIRE(our_clsid != free_threaded_marshaler); +} diff --git a/test/test/test.vcxproj b/test/test/test.vcxproj index 43928dab2..41b3e4d40 100644 --- a/test/test/test.vcxproj +++ b/test/test/test.vcxproj @@ -234,6 +234,7 @@ + NotUsing @@ -324,6 +325,8 @@ NotUsing + + diff --git a/test/test_cpp20/hstring_literal.cpp b/test/test_cpp20/hstring_literal.cpp new file mode 100644 index 000000000..2ba0f3ad7 --- /dev/null +++ b/test/test_cpp20/hstring_literal.cpp @@ -0,0 +1,68 @@ +#include "pch.h" + +using namespace winrt; +using namespace std::literals; + +#if defined(__cpp_nontype_template_args) && __cpp_nontype_template_args >= 201911L + +using namespace winrt::literals; + +namespace +{ + // Exercises the hstring_reference -> param::hstring conversion that projected + // setters rely on, and duplicates into an owning hstring on the way out. + winrt::hstring copy_via_param(winrt::param::hstring const& value) + { + winrt::hstring const& as_hstring = value; + return as_hstring; + } +} + +TEST_CASE("hstring_literal") +{ + // The literal is a genuine constant expression: the fast-pass header is built at + // compile time, so an hstring_reference can be constructed in a constexpr context. + { + constexpr winrt::hstring_reference lit = L"kittens"_hs; + winrt::hstring const& value = lit; + REQUIRE(value == L"kittens"sv); + REQUIRE(value.size() == 7); + } + + // Content and length match the literal. + { + winrt::hstring_reference const lit = L"kittens"_hs; + winrt::hstring const& value = lit; + REQUIRE(value == L"kittens"sv); + REQUIRE(value.size() == 7); + REQUIRE(wcslen(value.c_str()) == 7); + } + + // Built as a fast-pass reference string (no heap allocation). + { + winrt::hstring_reference const lit = L"puppies"_hs; + auto const header = static_cast(winrt::get_abi(lit)); + REQUIRE(header != nullptr); + REQUIRE((header->flags & winrt::impl::hstring_reference_flag) != 0); + REQUIRE(header->length == 7); + } + + // Empty literal projects as the empty (null) HSTRING. + { + winrt::hstring_reference const lit = L""_hs; + winrt::hstring const& value = lit; + REQUIRE(value.empty()); + REQUIRE(value.size() == 0); + REQUIRE(winrt::get_abi(value) == nullptr); + } + + // Binds to a projected setter parameter in a single conversion, and copying + // into an owning hstring duplicates correctly. + { + winrt::hstring const copied = copy_via_param(L"waffles"_hs); + REQUIRE(copied == L"waffles"sv); + REQUIRE(copied.size() == 7); + } +} + +#endif diff --git a/test/test_cpp20/test_cpp20.vcxproj b/test/test_cpp20/test_cpp20.vcxproj index 4eaee0a18..b13becd23 100644 --- a/test/test_cpp20/test_cpp20.vcxproj +++ b/test/test_cpp20/test_cpp20.vcxproj @@ -239,6 +239,7 @@ + NotUsing