diff --git a/.github/actions/setup-llvm-mingw/action.yml b/.github/actions/setup-llvm-mingw/action.yml index 50fe696b7..d36b48a75 100644 --- a/.github/actions/setup-llvm-mingw/action.yml +++ b/.github/actions/setup-llvm-mingw/action.yml @@ -19,7 +19,7 @@ runs: - name: Cache llvm-mingw (Windows) id: cache-llvm if: runner.os == 'Windows' - uses: actions/cache@v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: .llvm-mingw key: llvm-mingw-${{ runner.os }}-${{ inputs.llvm-mingw-version }}-${{ inputs.host-arch }} @@ -50,7 +50,7 @@ runs: - name: Cache llvm-mingw (Linux) id: cache-llvm-linux if: runner.os == 'Linux' - uses: actions/cache@v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: /opt/llvm-mingw key: llvm-mingw-${{ runner.os }}-${{ inputs.llvm-mingw-version }}-${{ inputs.host-arch }} diff --git a/.github/actions/setup-llvm-msvc/action.yml b/.github/actions/setup-llvm-msvc/action.yml index 036d0fa28..e5a696a9f 100644 --- a/.github/actions/setup-llvm-msvc/action.yml +++ b/.github/actions/setup-llvm-msvc/action.yml @@ -14,7 +14,7 @@ runs: steps: - name: Cache LLVM and tools id: cache-llvm - uses: actions/cache@v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: | .LLVM diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 47f88349d..9557baa7a 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -8,3 +8,5 @@ updates: directory: "/" schedule: interval: "daily" + cooldown: + default-days: 7 diff --git a/.github/workflows/check-line-endings.yml b/.github/workflows/check-line-endings.yml index dea958274..3891aec09 100644 --- a/.github/workflows/check-line-endings.yml +++ b/.github/workflows/check-line-endings.yml @@ -11,7 +11,7 @@ jobs: name: Enforce .gitattributes line endings runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Check for line ending violations run: | diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index afdeed462..5e2677fed 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,7 +26,7 @@ jobs: config: Release runs-on: ${{ matrix.toolchain.image }} steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Download nuget run: | @@ -71,7 +71,7 @@ jobs: cmd /c "$env:VSDevCmd" "&" msbuild /m /clp:ForceConsoleColor "$env:msbuild_config_props" cppwinrt.sln /t:cppwinrt - name: Upload built executables - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: msvc-build-${{ matrix.compiler}}-${{ matrix.arch }}-${{ matrix.config }}-${{ matrix.toolchain.platform_toolset }}-bin path: | @@ -113,18 +113,18 @@ jobs: test_exe: test_cpp20_module runs-on: ${{ matrix.toolchain.image }} steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Fetch cppwinrt executables if: matrix.arch != 'arm64' - uses: actions/download-artifact@v8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: msvc-build-${{ matrix.compiler}}-${{ matrix.arch }}-${{ matrix.config }}-${{ matrix.toolchain.platform_toolset }}-bin path: _build/${{ matrix.arch }}/${{ matrix.config }}/ - name: Fetch x86 cppwinrt executables (arm64 only) if: matrix.arch == 'arm64' - uses: actions/download-artifact@v8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: msvc-build-${{ matrix.compiler}}-x86-Release-${{ matrix.toolchain.platform_toolset }}-bin path: _build/x86/Release/ @@ -243,7 +243,7 @@ jobs: - name: Upload arm64 test executables if: matrix.arch == 'arm64' - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: msvc-tests-${{ matrix.test_exe }}-${{ matrix.compiler }}-${{ matrix.arch }}-${{ matrix.config }}-${{ matrix.toolchain.platform_toolset }}-bin path: | @@ -263,7 +263,7 @@ jobs: CMAKE_COLOR_DIAGNOSTICS: 1 CLICOLOR_FORCE: 1 steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Install cross compiler run: | @@ -282,7 +282,7 @@ jobs: cmake --build build/cross_x64/ --target install -j2 - name: Upload cppwinrt.exe - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: cross-build-${{ matrix.arch }}-bin path: install/bin/cppwinrt.exe @@ -296,7 +296,7 @@ jobs: Deployment: [Component, Standalone] runs-on: windows-2025-vs2026 steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Download nuget run: | @@ -339,10 +339,10 @@ jobs: platform_toolset: v145 runs-on: ${{ matrix.toolchain.image }} steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Fetch cppwinrt executables - uses: actions/download-artifact@v8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: msvc-build-${{ matrix.compiler}}-${{ matrix.arch }}-${{ matrix.config }}-${{ matrix.toolchain.platform_toolset }}-bin path: _build/${{ matrix.arch }}/${{ matrix.config }}/ @@ -384,7 +384,7 @@ jobs: name: Build nuget package with MSVC runs-on: windows-2025-vs2026 steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Package run: | @@ -399,7 +399,7 @@ jobs: } - name: Upload nuget package artifact - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: package path: "*.nupkg" diff --git a/cppwinrt/code_writers.h b/cppwinrt/code_writers.h index ab119ae89..a9aa5bc78 100644 --- a/cppwinrt/code_writers.h +++ b/cppwinrt/code_writers.h @@ -226,6 +226,17 @@ namespace cppwinrt return { w, write_close_namespace }; } + [[nodiscard]] static finish_with wrap_type_namespace_without_export(writer& w, std::string_view const& ns) + { + auto format = R"(namespace winrt::@ +{ +)"; + + w.write(format, ns); + + return { w, write_close_namespace }; + } + static void write_enum_field(writer& w, Field const& field) { auto format = R"( % = %, @@ -1464,24 +1475,36 @@ namespace cppwinrt else if (type_name == "Windows.Foundation.IAsyncAction") { w.write(R"( auto get() const; + // Synchronously waits without asserting that the calling thread is not an STA. + // Use only when the STA is not presenting UI and blocking is known to be safe. + auto get_unchecked() const; auto wait_for(Windows::Foundation::TimeSpan const& timeout) const; )"); } else if (type_name == "Windows.Foundation.IAsyncOperation`1") { w.write(R"( auto get() const; + // Synchronously waits without asserting that the calling thread is not an STA. + // Use only when the STA is not presenting UI and blocking is known to be safe. + auto get_unchecked() const; auto wait_for(Windows::Foundation::TimeSpan const& timeout) const; )"); } else if (type_name == "Windows.Foundation.IAsyncActionWithProgress`1") { w.write(R"( auto get() const; + // Synchronously waits without asserting that the calling thread is not an STA. + // Use only when the STA is not presenting UI and blocking is known to be safe. + auto get_unchecked() const; auto wait_for(Windows::Foundation::TimeSpan const& timeout) const; )"); } else if (type_name == "Windows.Foundation.IAsyncOperationWithProgress`2") { w.write(R"( auto get() const; + // Synchronously waits without asserting that the calling thread is not an STA. + // Use only when the STA is not presenting UI and blocking is known to be safe. + auto get_unchecked() const; auto wait_for(Windows::Foundation::TimeSpan const& timeout) const; )"); } diff --git a/cppwinrt/component_writers.h b/cppwinrt/component_writers.h index af5626d14..94e20e53e 100644 --- a/cppwinrt/component_writers.h +++ b/cppwinrt/component_writers.h @@ -400,7 +400,7 @@ catch (...) { return winrt::to_hresult(); } return; } - auto wrap_type = wrap_type_namespace(w, type_namespace); + auto wrap_type = wrap_type_namespace_without_export(w, type_namespace); for (auto&&[factory_name, factory] : get_factories(w, type)) { diff --git a/strings/base_coroutine_foundation.h b/strings/base_coroutine_foundation.h index 5cefad836..82582ee64 100644 --- a/strings/base_coroutine_foundation.h +++ b/strings/base_coroutine_foundation.h @@ -99,6 +99,19 @@ WINRT_EXPORT namespace winrt::impl return async.GetResults(); } + template + auto wait_get_bypass_sta_check(Async const& async) + { + auto status = async.Status(); + if (status == Windows::Foundation::AsyncStatus::Started) + { + status = wait_for_completed(async, 0xFFFFFFFF); // INFINITE + } + check_status_canceled(status); + + return async.GetResults(); + } + #ifdef WINRT_IMPL_COROUTINES struct ignore_apartment_context {}; @@ -220,6 +233,11 @@ WINRT_EXPORT namespace winrt::impl impl::wait_get(static_cast(static_cast(*this))); } template + auto consume_Windows_Foundation_IAsyncAction::get_unchecked() const + { + impl::wait_get_bypass_sta_check(static_cast(static_cast(*this))); + } + template auto consume_Windows_Foundation_IAsyncAction::wait_for(Windows::Foundation::TimeSpan const& timeout) const { return impl::wait_for(static_cast(static_cast(*this)), timeout); @@ -231,6 +249,11 @@ WINRT_EXPORT namespace winrt::impl return impl::wait_get(static_cast const&>(static_cast(*this))); } template + auto consume_Windows_Foundation_IAsyncOperation::get_unchecked() const + { + return impl::wait_get_bypass_sta_check(static_cast const&>(static_cast(*this))); + } + template auto consume_Windows_Foundation_IAsyncOperation::wait_for(Windows::Foundation::TimeSpan const& timeout) const { return impl::wait_for(static_cast const&>(static_cast(*this)), timeout); @@ -242,6 +265,11 @@ WINRT_EXPORT namespace winrt::impl impl::wait_get(static_cast const&>(static_cast(*this))); } template + auto consume_Windows_Foundation_IAsyncActionWithProgress::get_unchecked() const + { + impl::wait_get_bypass_sta_check(static_cast const&>(static_cast(*this))); + } + template auto consume_Windows_Foundation_IAsyncActionWithProgress::wait_for(Windows::Foundation::TimeSpan const& timeout) const { return impl::wait_for(static_cast const&>(static_cast(*this)), timeout); @@ -253,6 +281,11 @@ WINRT_EXPORT namespace winrt::impl return impl::wait_get(static_cast const&>(static_cast(*this))); } template + auto consume_Windows_Foundation_IAsyncOperationWithProgress::get_unchecked() const + { + return impl::wait_get_bypass_sta_check(static_cast const&>(static_cast(*this))); + } + template auto consume_Windows_Foundation_IAsyncOperationWithProgress::wait_for(Windows::Foundation::TimeSpan const& timeout) const { return impl::wait_for(static_cast const&>(static_cast(*this)), timeout); diff --git a/strings/base_coroutine_threadpool.h b/strings/base_coroutine_threadpool.h index 057d5b548..c901b94ba 100644 --- a/strings/base_coroutine_threadpool.h +++ b/strings/base_coroutine_threadpool.h @@ -146,32 +146,42 @@ WINRT_EXPORT namespace winrt void set_canceller(canceller_t canceller, void* context) { m_context = context; - m_canceller.store(canceller, std::memory_order_release); + canceller_t expected = nullptr; + m_canceller.compare_exchange_strong(expected, canceller, std::memory_order_release, std::memory_order_relaxed); } void revoke_canceller() { - while (m_canceller.exchange(nullptr, std::memory_order_acquire) == cancelling_ptr) + auto existing = m_canceller.load(std::memory_order_relaxed); + do { - std::this_thread::yield(); + while (existing == cancelling_ptr) + { + std::this_thread::yield(); + existing = m_canceller.load(std::memory_order_relaxed); + } } + while (!m_canceller.compare_exchange_weak(existing, nullptr, std::memory_order_acquire, std::memory_order_relaxed)); } void cancel() { auto canceller = m_canceller.exchange(cancelling_ptr, std::memory_order_acquire); - struct unique_cancellation_lock + if (canceller != cancelling_ptr) { - cancellable_promise* promise; - ~unique_cancellation_lock() + struct unique_cancellation_lock + { + cancellable_promise* promise; + ~unique_cancellation_lock() + { + promise->m_canceller.store(nullptr, std::memory_order_release); + } + } lock{ this }; + + if (canceller != nullptr) { - promise->m_canceller.store(nullptr, std::memory_order_release); + canceller(m_context); } - } lock{ this }; - - if ((canceller != nullptr) && (canceller != cancelling_ptr)) - { - canceller(m_context); } } diff --git a/strings/base_includes.h b/strings/base_includes.h index 287a09709..bac7358bd 100644 --- a/strings/base_includes.h +++ b/strings/base_includes.h @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include diff --git a/strings/base_macros.h b/strings/base_macros.h index 91b0121d9..850d0c627 100644 --- a/strings/base_macros.h +++ b/strings/base_macros.h @@ -44,7 +44,7 @@ // Template specializations in namespace std (hash, coroutine_traits) need extern "C++" // linkage in module builds for proper merging with the std module, but must NOT be -// exported — exporting namespace std would make all of std transitively visible. +// exported - exporting namespace std would make all of std transitively visible. #ifndef WINRT_IMPL_STD_EXPORT #ifdef WINRT_IMPL_BUILD_MODULE #define WINRT_IMPL_STD_EXPORT extern "C++" diff --git a/strings/base_reference_produce.h b/strings/base_reference_produce.h index abffb3384..db9639cc3 100644 --- a/strings/base_reference_produce.h +++ b/strings/base_reference_produce.h @@ -2,99 +2,197 @@ WINRT_EXPORT namespace winrt::impl { template - struct reference : implements, Windows::Foundation::IReference, Windows::Foundation::IPropertyValue> + struct reference; + + template + struct reference_array; + + 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; + + template + constexpr Windows::Foundation::PropertyType scalar_property_type() noexcept + { + 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::Inspectable; } + 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 if constexpr (std::is_same_v) { return pt::Size; } + else if constexpr (std::is_same_v) { return pt::Rect; } + else { return pt::OtherType; } + } + + template + constexpr Windows::Foundation::PropertyType array_property_type() noexcept { - reference(T const& value) : m_value(value) - { - } + return static_cast( + static_cast(scalar_property_type()) + 1024); + } - T Value() const - { - return m_value; - } + template + inline constexpr bool is_numeric_scalar_v = + (std::is_arithmetic_v && !std::is_same_v && !std::is_same_v) || std::is_enum_v; + template + struct reference_producer : implements, non_agile, marker>> + { Windows::Foundation::PropertyType Type() const noexcept { - return Windows::Foundation::PropertyType::OtherType; + if constexpr (IsArray) { return array_property_type(); } + else { return scalar_property_type(); } } static constexpr bool IsNumericScalar() noexcept { - return std::is_arithmetic_v || std::is_enum_v; + return !IsArray && is_numeric_scalar_v; } - std::uint8_t GetUInt8() const - { - return to_scalar(); - } + std::uint8_t GetUInt8() const { return derived()->template get_as(); } + std::int16_t GetInt16() const { return derived()->template get_as(); } + std::uint16_t GetUInt16() const { return derived()->template get_as(); } + std::int32_t GetInt32() const { return derived()->template get_as(); } + std::uint32_t GetUInt32() const { return derived()->template get_as(); } + std::int64_t GetInt64() const { return derived()->template get_as(); } + std::uint64_t GetUInt64() const { return derived()->template get_as(); } + float GetSingle() const { return derived()->template get_as(); } + double GetDouble() const { return derived()->template get_as(); } + char16_t GetChar16() const { return derived()->template get_as(); } + bool GetBoolean() const { return derived()->template get_as(); } + hstring GetString() const { return derived()->template get_as(); } + guid GetGuid() const { return derived()->template get_as(); } + Windows::Foundation::DateTime GetDateTime() const { return derived()->template get_as(); } + Windows::Foundation::TimeSpan GetTimeSpan() const { return derived()->template get_as(); } + Windows::Foundation::Point GetPoint() const { return derived()->template get_as(); } + Windows::Foundation::Size GetSize() const { return derived()->template get_as(); } + Windows::Foundation::Rect GetRect() const { return derived()->template get_as(); } + void GetUInt8Array(com_array& value) const { derived()->get_as(value); } + void GetInt16Array(com_array& value) const { derived()->get_as(value); } + void GetUInt16Array(com_array& value) const { derived()->get_as(value); } + void GetInt32Array(com_array& value) const { derived()->get_as(value); } + void GetUInt32Array(com_array& value) const { derived()->get_as(value); } + void GetInt64Array(com_array& value) const { derived()->get_as(value); } + void GetUInt64Array(com_array& value) const { derived()->get_as(value); } + void GetSingleArray(com_array& value) const { derived()->get_as(value); } + void GetDoubleArray(com_array& value) const { derived()->get_as(value); } + void GetChar16Array(com_array& value) const { derived()->get_as(value); } + void GetBooleanArray(com_array& value) const { derived()->get_as(value); } + void GetStringArray(com_array& value) const { derived()->get_as(value); } + void GetInspectableArray(com_array& value) const { derived()->get_as(value); } + void GetGuidArray(com_array& value) const { derived()->get_as(value); } + void GetDateTimeArray(com_array& value) const { derived()->get_as(value); } + void GetTimeSpanArray(com_array& value) const { derived()->get_as(value); } + void GetPointArray(com_array& value) const { derived()->get_as(value); } + void GetSizeArray(com_array& value) const { derived()->get_as(value); } + void GetRectArray(com_array& value) const { derived()->get_as(value); } - std::int16_t GetInt16() const - { - return to_scalar(); - } + private: - std::uint16_t GetUInt16() const - { - return to_scalar(); - } + Derived const* derived() const noexcept { return static_cast(this); } - std::int32_t GetInt32() const + std::int32_t query_interface_tearoff(guid const& id, void** object) const noexcept override { - return to_scalar(); - } + if constexpr (is_stock_reference_v) + { + if (is_guid_of(id)) + { + try + { + auto marshal = derived()->create_property_value().template as(); + *object = detach_abi(marshal); + return error_ok; + } + catch (...) + { + *object = nullptr; + return to_hresult(); + } + } - std::uint32_t GetUInt32() const - { - return to_scalar(); + if (is_guid_of(id)) + { + auto unknown = reinterpret_cast(to_abi(derived())); + unknown->AddRef(); + *object = unknown; + return error_ok; + } + } + + *object = nullptr; + return error_no_interface; } + }; - std::int64_t GetInt64() const + template + struct reference : reference_producer, T, Windows::Foundation::IReference, false> + { + reference(T const& value) : m_value(value) { - return to_scalar(); } - std::uint64_t GetUInt64() const + T Value() const { - return to_scalar(); + return m_value; } - 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(); } - Windows::Foundation::Size GetSize() { throw hresult_not_implemented(); } - Windows::Foundation::Rect GetRect() { throw hresult_not_implemented(); } - void GetUInt8Array(com_array &) { throw hresult_not_implemented(); } - void GetInt16Array(com_array &) { throw hresult_not_implemented(); } - void GetUInt16Array(com_array &) { throw hresult_not_implemented(); } - void GetInt32Array(com_array &) { throw hresult_not_implemented(); } - void GetUInt32Array(com_array &) { throw hresult_not_implemented(); } - void GetInt64Array(com_array &) { throw hresult_not_implemented(); } - void GetUInt64Array(com_array &) { throw hresult_not_implemented(); } - void GetSingleArray(com_array &) { throw hresult_not_implemented(); } - void GetDoubleArray(com_array &) { throw hresult_not_implemented(); } - void GetChar16Array(com_array &) { throw hresult_not_implemented(); } - void GetBooleanArray(com_array &) { throw hresult_not_implemented(); } - void GetStringArray(com_array &) { throw hresult_not_implemented(); } - void GetInspectableArray(com_array &) { throw hresult_not_implemented(); } - void GetGuidArray(com_array &) { throw hresult_not_implemented(); } - void GetDateTimeArray(com_array &) { throw hresult_not_implemented(); } - void GetTimeSpanArray(com_array &) { throw hresult_not_implemented(); } - void GetPointArray(com_array &) { throw hresult_not_implemented(); } - void GetSizeArray(com_array &) { throw hresult_not_implemented(); } - void GetRectArray(com_array &) { throw hresult_not_implemented(); } - private: + template friend struct reference_producer; + + 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 + To get_as() const { - if constexpr (IsNumericScalar()) + if constexpr (std::is_same_v) + { + return m_value; + } + else if constexpr (is_numeric_scalar_v && is_numeric_scalar_v) { return static_cast(m_value); } @@ -104,98 +202,81 @@ WINRT_EXPORT namespace winrt::impl } } + template + void get_as(com_array const&) const + { + throw hresult_not_implemented(); + } + T m_value; }; template - struct reference_traits - { - static auto make(T const& value) { return winrt::make>(value); } - 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 + struct reference_array : reference_producer, T, Windows::Foundation::IReferenceArray, true> { - static auto make(std::int16_t value) { return Windows::Foundation::PropertyValue::CreateInt16(value); } - using itf = Windows::Foundation::IReference; - }; + reference_array(array_view const& value) : m_value(value.begin(), value.end()) + { + } - template <> - struct reference_traits - { - static auto make(std::uint32_t value) { return Windows::Foundation::PropertyValue::CreateUInt32(value); } - using itf = Windows::Foundation::IReference; - }; + com_array Value() const + { + return com_array(m_value.begin(), m_value.end()); + } - template <> - struct reference_traits - { - static auto make(std::int32_t value) { return Windows::Foundation::PropertyValue::CreateInt32(value); } - using itf = Windows::Foundation::IReference; - }; + private: - template <> - struct reference_traits - { - static auto make(std::uint64_t value) { return Windows::Foundation::PropertyValue::CreateUInt64(value); } - using itf = Windows::Foundation::IReference; - }; + template friend struct reference_producer; - 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; - }; + Windows::Foundation::IInspectable create_property_value() const + { + using pv = Windows::Foundation::PropertyValue; + + if constexpr (std::is_same_v) { return pv::CreateUInt8Array(m_value); } + else if constexpr (std::is_same_v) { return pv::CreateInt16Array(m_value); } + else if constexpr (std::is_same_v) { return pv::CreateUInt16Array(m_value); } + else if constexpr (std::is_same_v) { return pv::CreateInt32Array(m_value); } + else if constexpr (std::is_same_v) { return pv::CreateUInt32Array(m_value); } + else if constexpr (std::is_same_v) { return pv::CreateInt64Array(m_value); } + else if constexpr (std::is_same_v) { return pv::CreateUInt64Array(m_value); } + else if constexpr (std::is_same_v) { return pv::CreateSingleArray(m_value); } + else if constexpr (std::is_same_v) { return pv::CreateDoubleArray(m_value); } + else if constexpr (std::is_same_v) { return pv::CreateChar16Array(m_value); } + else if constexpr (std::is_same_v) { return pv::CreateBooleanArray(m_value); } + else if constexpr (std::is_same_v) { return pv::CreateStringArray(m_value); } + else if constexpr (std::is_same_v) { return pv::CreateGuidArray(m_value); } + else if constexpr (std::is_same_v) { return pv::CreateDateTimeArray(m_value); } + else if constexpr (std::is_same_v) { return pv::CreateTimeSpanArray(m_value); } + else if constexpr (std::is_same_v) { return pv::CreatePointArray(m_value); } + else { return nullptr; } + } - template <> - struct reference_traits - { - static auto make(double value) { return Windows::Foundation::PropertyValue::CreateDouble(value); } - using itf = Windows::Foundation::IReference; - }; + template + To get_as() const + { + throw hresult_not_implemented(); + } - template <> - struct reference_traits - { - static auto make(char16_t value) { return Windows::Foundation::PropertyValue::CreateChar16(value); } - using itf = Windows::Foundation::IReference; - }; + template + void get_as(com_array& value) const + { + if constexpr (std::is_same_v) + { + value = com_array(m_value.begin(), m_value.end()); + } + else + { + throw hresult_not_implemented(); + } + } - template <> - struct reference_traits - { - static auto make(bool value) { return Windows::Foundation::PropertyValue::CreateBoolean(value); } - using itf = Windows::Foundation::IReference; + com_array m_value; }; - template <> - struct reference_traits + template + struct reference_traits { - static auto make(hstring const& value) { return Windows::Foundation::PropertyValue::CreateString(value); } - using itf = Windows::Foundation::IReference; + static auto make(T const& value) { return winrt::make>(value); } + using itf = Windows::Foundation::IReference; }; template <> @@ -205,41 +286,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 { @@ -257,77 +310,77 @@ WINRT_EXPORT namespace winrt::impl template <> struct reference_traits> { - static auto make(array_view const& value) { return Windows::Foundation::PropertyValue::CreateUInt8Array(value); } + static auto make(array_view const& value) { return winrt::make>(value); } using itf = Windows::Foundation::IReferenceArray; }; template <> struct reference_traits> { - static auto make(array_view const& value) { return Windows::Foundation::PropertyValue::CreateInt16Array(value); } + static auto make(array_view const& value) { return winrt::make>(value); } using itf = Windows::Foundation::IReferenceArray; }; template <> struct reference_traits> { - static auto make(array_view const& value) { return Windows::Foundation::PropertyValue::CreateUInt16Array(value); } + static auto make(array_view const& value) { return winrt::make>(value); } using itf = Windows::Foundation::IReferenceArray; }; template <> struct reference_traits> { - static auto make(array_view const& value) { return Windows::Foundation::PropertyValue::CreateInt32Array(value); } + static auto make(array_view const& value) { return winrt::make>(value); } using itf = Windows::Foundation::IReferenceArray; }; template <> struct reference_traits> { - static auto make(com_array const& value) { return Windows::Foundation::PropertyValue::CreateUInt32Array(value); } + static auto make(com_array const& value) { return winrt::make>(value); } using itf = Windows::Foundation::IReferenceArray; }; template <> struct reference_traits> { - static auto make(array_view const& value) { return Windows::Foundation::PropertyValue::CreateInt64Array(value); } + static auto make(array_view const& value) { return winrt::make>(value); } using itf = Windows::Foundation::IReferenceArray; }; template <> struct reference_traits> { - static auto make(array_view const& value) { return Windows::Foundation::PropertyValue::CreateUInt64Array(value); } + static auto make(array_view const& value) { return winrt::make>(value); } using itf = Windows::Foundation::IReferenceArray; }; template <> struct reference_traits> { - static auto make(array_view const& value) { return Windows::Foundation::PropertyValue::CreateSingleArray(value); } + static auto make(array_view const& value) { return winrt::make>(value); } using itf = Windows::Foundation::IReferenceArray; }; template <> struct reference_traits> { - static auto make(array_view const& value) { return Windows::Foundation::PropertyValue::CreateDoubleArray(value); } + static auto make(array_view const& value) { return winrt::make>(value); } using itf = Windows::Foundation::IReferenceArray; }; template <> struct reference_traits> { - static auto make(array_view const& value) { return Windows::Foundation::PropertyValue::CreateChar16Array(value); } + static auto make(array_view const& value) { return winrt::make>(value); } using itf = Windows::Foundation::IReferenceArray; }; template <> struct reference_traits> { - static auto make(array_view const& value) { return Windows::Foundation::PropertyValue::CreateBooleanArray(value); } + static auto make(array_view const& value) { return winrt::make>(value); } using itf = Windows::Foundation::IReferenceArray; }; @@ -348,14 +401,14 @@ WINRT_EXPORT namespace winrt::impl template <> struct reference_traits> { - static auto make(array_view const& value) { return Windows::Foundation::PropertyValue::CreateGuidArray(value); } + static auto make(array_view const& value) { return winrt::make>(value); } using itf = Windows::Foundation::IReferenceArray; }; template <> struct reference_traits> { - static auto make(array_view const& value) { return Windows::Foundation::PropertyValue::CreateGuidArray(reinterpret_cast const&>(value)); } + static auto make(array_view const& value) { return winrt::make>(reinterpret_cast const&>(value)); } using itf = Windows::Foundation::IReferenceArray; }; @@ -376,7 +429,7 @@ WINRT_EXPORT namespace winrt::impl template <> struct reference_traits> { - static auto make(array_view const& value) { return Windows::Foundation::PropertyValue::CreatePointArray(value); } + static auto make(array_view const& value) { return winrt::make>(value); } using itf = Windows::Foundation::IReferenceArray; }; diff --git a/strings/base_string.h b/strings/base_string.h index 6b1fb37b5..50a248c02 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 +{ + 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 + { + static_assert(Literal.value[Literal.size - 1] == L'\0', "_hs requires a null-terminated wide string literal"); + + 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/reference_boxing.cpp b/test/test/reference_boxing.cpp new file mode 100644 index 000000000..c6e300886 --- /dev/null +++ b/test/test/reference_boxing.cpp @@ -0,0 +1,268 @@ +#include "pch.h" +#include +#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); + // A scalar reference holds no array, so every array getter routes through get_as and throws. + { + com_array ints; + REQUIRE_THROWS_AS(pv.GetInt32Array(ints), hresult_not_implemented); + com_array strings; + REQUIRE_THROWS_AS(pv.GetStringArray(strings), hresult_not_implemented); + } + 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); + } +} + +// Array boxing produces a local IReferenceArray / IPropertyValue (no combase PropertyValue) for the +// stock element types. Confirm the array PropertyType, round-trips, and the get_as throw behavior. +TEST_CASE("reference_boxing arrays") +{ + { + int32_t values[]{ 0, 42, 1729, -1 }; + auto boxed = box_value(com_array{ std::begin(values), std::end(values) }); + auto pv = boxed.as(); + REQUIRE(pv.Type() == PropertyType::Int32Array); + REQUIRE(!pv.IsNumericScalar()); + + com_array out; + pv.GetInt32Array(out); + REQUIRE(out == array_view{ values }); + + // A scalar getter on an array PV throws, and so does a mismatched-element array getter. + REQUIRE_THROWS_AS(pv.GetInt32(), hresult_not_implemented); + com_array wrong; + REQUIRE_THROWS_AS(pv.GetDoubleArray(wrong), hresult_not_implemented); + + REQUIRE(unbox_value>(boxed) == array_view{ values }); + REQUIRE(boxed.as>().Value() == array_view{ values }); + } + + // guid arrays are local too. + { + guid values[]{ + { 0x11223344, 0x5566, 0x7788, { 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00 } }, + { 0x00112233, 0x4455, 0x6677, { 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF } } }; + auto boxed = box_value(com_array{ std::begin(values), std::end(values) }); + REQUIRE(boxed.as().Type() == PropertyType::GuidArray); + REQUIRE(unbox_value>(boxed) == array_view{ values }); + } +} + +// The local array reference must marshal by value across processes just like the scalar one: its +// IMarshal reports the same unmarshal class as a genuine combase array PropertyValue, and not the +// free-threaded (by-reference) class. +TEST_CASE("reference_boxing array marshal by value") +{ + int32_t values[]{ 1, 2, 3 }; + auto boxed = box_value(com_array{ std::begin(values), std::end(values) }); + REQUIRE(boxed.try_as()); + auto ours = boxed.as(); + + auto genuine = PropertyValue::CreateInt32Array(values); + 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); + + guid const free_threaded_marshaler{ 0x0000033A, 0x0000, 0x0000, { 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46 } }; + REQUIRE(our_clsid != free_threaded_marshaler); +} + +// 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); +} + +// The in-proc reference advertises IAgileObject, so handing it between two single-threaded +// apartments in the same process must resolve to the *same* object pointer - no proxy. The Global +// Interface Table returns an agile object's original pointer directly, but hands back a proxy (a +// different identity) for a non-agile object, so pointer equality here confirms the agile fast path. +TEST_CASE("reference_boxing agile in-proc identity across apartments") +{ + auto identity_of = [](::IUnknown* raw) -> void* + { + com_ptr<::IUnknown> identity; + check_hresult(raw->QueryInterface(IID_PPV_ARGS(identity.put()))); + return identity.get(); + }; + + com_ptr git; + check_hresult(CoCreateInstance(CLSID_StdGlobalInterfaceTable, nullptr, + CLSCTX_INPROC_SERVER, IID_PPV_ARGS(git.put()))); + + Windows::Foundation::IInspectable boxed{ nullptr }; + DWORD cookie{}; + void* original_identity{}; + void* marshaled_identity{}; + HRESULT sta1_hr = S_OK; + HRESULT sta2_hr = S_OK; + + handle registered{ check_pointer(CreateEventW(nullptr, true, false, nullptr)) }; + handle fetched{ check_pointer(CreateEventW(nullptr, true, false, nullptr)) }; + + std::thread sta1([&] + { + sta1_hr = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + if (SUCCEEDED(sta1_hr)) + { + boxed = box_value(42); + auto unknown = reinterpret_cast<::IUnknown*>(get_abi(boxed)); + original_identity = identity_of(unknown); + sta1_hr = git->RegisterInterfaceInGlobal(unknown, IID_IUnknown, &cookie); + } + SetEvent(registered.get()); + + WaitForSingleObject(fetched.get(), INFINITE); + if (SUCCEEDED(sta1_hr)) + { + CoUninitialize(); + } + }); + + std::thread sta2([&] + { + WaitForSingleObject(registered.get(), INFINITE); + if (SUCCEEDED(sta1_hr)) + { + sta2_hr = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + if (SUCCEEDED(sta2_hr)) + { + ::IUnknown* raw{}; + sta2_hr = git->GetInterfaceFromGlobal(cookie, IID_IUnknown, reinterpret_cast(&raw)); + if (SUCCEEDED(sta2_hr)) + { + marshaled_identity = identity_of(raw); + raw->Release(); + } + git->RevokeInterfaceFromGlobal(cookie); + CoUninitialize(); + } + } + SetEvent(fetched.get()); + }); + + sta1.join(); + sta2.join(); + + REQUIRE(SUCCEEDED(sta1_hr)); + REQUIRE(SUCCEEDED(sta2_hr)); + REQUIRE(original_identity != nullptr); + REQUIRE(original_identity == marshaled_identity); +} diff --git a/test/test/test.vcxproj b/test/test/test.vcxproj index 43928dab2..f95584d99 100644 --- a/test/test/test.vcxproj +++ b/test/test/test.vcxproj @@ -324,6 +324,7 @@ 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 diff --git a/test/test_nocoro/get.cpp b/test/test_nocoro/get.cpp index 11339e10b..4fd85fe3e 100644 --- a/test/test_nocoro/get.cpp +++ b/test/test_nocoro/get.cpp @@ -2,6 +2,7 @@ using namespace winrt; using namespace Windows::Foundation; +using namespace Windows::Storage; template struct async_completion_source : implements, IAsyncOperation, IAsyncInfo> @@ -72,3 +73,42 @@ TEST_CASE("get") REQUIRE(acs.as>().get() == 0xDEADBEEF); } + +TEST_CASE("get_unchecked") +{ + // Call a real WinRT async operation from an STA thread. + // This is the scenario the new API is designed for: an STA that is not + // presenting UI, where a synchronous blocking wait is safe. + std::exception_ptr failure{}; + bool content_available = false; + std::thread sta_thread([&failure, &content_available] + { + try + { + winrt::init_apartment(winrt::apartment_type::single_threaded); + struct apartment_guard + { + ~apartment_guard() + { + winrt::uninit_apartment(); + } + } guard; + + auto content = PathIO::ReadTextAsync(L"C:\\Windows\\win.ini").get_unchecked(); + content_available = content.size() > 0; + } + catch (...) + { + failure = std::current_exception(); + } + }); + + sta_thread.join(); + + if (failure) + { + std::rethrow_exception(failure); + } + + REQUIRE(content_available); +} diff --git a/test/test_nocoro/pch.h b/test/test_nocoro/pch.h index 7ff48a37c..30a6d3079 100644 --- a/test/test_nocoro/pch.h +++ b/test/test_nocoro/pch.h @@ -2,5 +2,6 @@ #include "catch.hpp" #include "winrt/Windows.Foundation.h" +#include "winrt/Windows.Storage.h" using namespace std::literals;