From d63111c1251f244d018da4cb3fb8d66caff49455 Mon Sep 17 00:00:00 2001 From: Raymond Chen Date: Thu, 14 Sep 2023 21:09:49 -0700 Subject: [PATCH 1/2] wil::to_array_view provides safer access to IBuffer / IMemoryBuffer The existing `.data()` member gives you a pointer to the start of the buffer but you are on your own to get the buffer size by other means. The new `wil::to_array_view` function returns the buffer bytes in the form of a `winrt::array_view`, which encodes both the start of the buffer as well as its length. By default, you get an `array_view`, but you can customize the data type by calling `to_array_view` to get an `array_view`. Any partial `T`s are ignored. For example, if the buffer is 6 bytes long, then calling `to_array_view` will give you a view of size 1. To get the `IMemoryBuffer` and `IMemoryBufferReference` versions, you must include `winrt/Windows.Foundation.h` before `wil/cppwinrt_helpers.h`, and you must include `MemoryBuffer.h` at some point before using `to_array_view`. To get the `IBuffer` version, you must include `winrt/Windows.Storage.Streams.h` before `wil/cppwinrt_helpers.h`. The `IBuffer` version defaults to returning an `array_view` for the buffer's length. If you want an `array_view` for the buffer's capacity, you can use `to_array_view_for_capacity`. We follow the pattern of allowing `cppwinrt_helpers.h` to be included multiple times, and each time you do it, new features light up based on what headers you have included prior to including `cppwinrt_helpers.h`. --- include/wil/cppwinrt_helpers.h | 70 ++++++++++++++++++++++++++++++++++ tests/CppWinRTTests.cpp | 38 ++++++++++++++++++ 2 files changed, 108 insertions(+) diff --git a/include/wil/cppwinrt_helpers.h b/include/wil/cppwinrt_helpers.h index c46a73e0d..b2d4bcaa3 100644 --- a/include/wil/cppwinrt_helpers.h +++ b/include/wil/cppwinrt_helpers.h @@ -212,6 +212,48 @@ namespace wil::details #endif // __WIL_CPPWINRT_MICROSOFT_UI_DISPATCHING_HELPERS /// @endcond +#if defined(WINRT_Windows_Foundation_H) && !defined(__WIL_CPPWINRT_WINDOWS_FOUNDATION_HELPERS) +#define __WIL_CPPWINRT_WINDOWS_FOUNDATION_HELPERS +namespace Windows::Foundation +{ + /// @cond + struct IMemoryBufferByteAccess; + /// @endcond +} + +namespace wil +{ + //! Returns a view into the underlying bytes of a memory buffer + //! provided in the form of an IMemoryBufferReference. + //! The caller is responsible for ensuring that the memory buffer's + //! lifetime encompasses the lifetime of the returned view. + //! By default, returns an array_view, but you can provide an alternate + //! type such as to_array_view. + //! You must include memorybuffer.h in order to use this overload of to_array_view. + template + winrt::array_view to_array_view(winrt::Windows::Foundation::IMemoryBufferReference const& reference) + { + uint8_t* data; + uint32_t capacity; + winrt::check_hresult(reference.as>()->GetBuffer(&data, &capacity)); + return { reinterpret_cast(data), static_cast(capacity / sizeof(T)) }; + } + + //! Returns a view into the underlying bytes of a memory buffer + //! provided in the form of an IMemoryBuffer. + //! The caller is responsible for ensuring that the memory buffer's + //! lifetime encompasses the lifetime of the returned view. + //! By default, returns an array_view, but you can provide an alternate + //! type such as to_array_view. + //! You must include memorybuffer.h in order to use this overload of to_array_view. + template + winrt::array_view to_array_view(winrt::Windows::Foundation::IMemoryBuffer const& buffer) + { + return to_array_view(buffer.CreateReference()); + } +} +#endif + #if defined(WINRT_Windows_Foundation_Collections_H) && !defined(__WIL_CPPWINRT_WINDOWS_FOUNDATION_COLLECTION_HELPERS) #define __WIL_CPPWINRT_WINDOWS_FOUNDATION_COLLECTION_HELPERS namespace wil @@ -314,6 +356,34 @@ namespace wil } #endif +#if defined(WINRT_Windows_Storage_Streams_H) && !defined(__WIL_CPPWINRT_WINDOWS_STORAGE_STREAMS_HELPERS) +#define __WIL_CPPWINRT_WINDOWS_STORAGE_STREAMS_HELPERS +namespace wil +{ + //! Returns a view into the underlying bytes of an IBuffer up to its Length. + //! The caller is responsible for ensuring that the IBuffer's + //! lifetime encompasses the lifetime of the returned view. + //! By default, returns an array_view, but you can provide an alternate + //! type such as to_array_view. + template + winrt::array_view to_array_view(winrt::Windows::Storage::Streams::IBuffer const& buffer) + { + return { reinterpret_cast(buffer.data()), static_cast(buffer.Length() / sizeof(T)) }; + } + + //! Returns a view into the underlying bytes of an IBuffer up to its Capacity. + //! The caller is responsible for ensuring that the IBuffer's + //! lifetime encompasses the lifetime of the returned view. + //! By default, returns an array_view, but you can provide an alternate + //! type such as to_array_view. + template + winrt::array_view to_array_view_for_capacity(winrt::Windows::Storage::Streams::IBuffer const& buffer) + { + return { reinterpret_cast(buffer.data()), static_cast(buffer.Capacity() / sizeof(T)) }; + } +} +#endif + #if defined(WINRT_Windows_UI_H) && defined(_WINDOWS_UI_INTEROP_H_) && !defined(__WIL_CPPWINRT_WINDOWS_UI_INTEROP_HELPERS) #define __WIL_CPPWINRT_WINDOWS_UI_INTEROP_HELPERS #if !defined(____x_ABI_CWindows_CFoundation_CIClosable_FWD_DEFINED__) && !defined(MIDL_NS_PREFIX) diff --git a/tests/CppWinRTTests.cpp b/tests/CppWinRTTests.cpp index 210ad8586..4e97cc086 100644 --- a/tests/CppWinRTTests.cpp +++ b/tests/CppWinRTTests.cpp @@ -9,7 +9,9 @@ #include #include #include +#include #include // Verify can include a second time to unlock more features +#include using namespace winrt::Windows::ApplicationModel::Activation; @@ -182,6 +184,42 @@ TEST_CASE("CppWinRTTests::VectorToVector", "[cppwinrt]") winrt::uninit_apartment(); } +TEST_CASE("CppWinRTTests::BufferToArrayView", "[cppwinrt]") +{ + // Create a buffer of capacity 8 and length 4. + auto buffer = winrt::Windows::Storage::Streams::Buffer(8); + buffer.Length(4); + // Get a Capacity-based int view and set the ints to 256 and 512. + { + auto view = wil::to_array_view_for_capacity(buffer); + REQUIRE(view.size() == 2); + view[0] = 256; + view[1] = 512; + } + // Get a Length-based byte view and confirm that the four bytes are { 0, 1, 0, 0 }. + // (Assumes little-endian system.) + { + auto view = wil::to_array_view(buffer); + REQUIRE(view.size() == 4); + REQUIRE(view == winrt::array_view(std::array{ 0, 1, 0, 0 })); + } + // Create an IMemoryBuffer around the Buffer. + auto mbuffer = winrt::Windows::Storage::Streams::Buffer::CreateMemoryBufferOverIBuffer(buffer); + // Verify that the buffer is the 2 ints 256 and 512. + { + auto view = wil::to_array_view(mbuffer); + REQUIRE(view.size() == 2); + REQUIRE(view == winrt::array_view(std::array{ 256, 512 })); + } + // Verify that the buffer reference is the 8 bytes { 0, 1, 0, 0, 0, 2, 0, 0 }. + // (Assumes little-endian system.) + { + auto view = wil::to_array_view(mbuffer.CreateReference()); + REQUIRE(view.size() == 8); + REQUIRE(view == winrt::array_view(std::array{ 0, 1, 0, 0, 0, 2, 0, 0 })); + } +} + TEST_CASE("CppWinRTTests::WilToCppWinRTExceptionTranslationTest", "[cppwinrt]") { auto test = [](HRESULT hr) From dc1a172288f882895115cde4031683274365adbe Mon Sep 17 00:00:00 2001 From: Raymond Chen Date: Sun, 17 Sep 2023 15:49:50 -0700 Subject: [PATCH 2/2] Simpler way of making IMemoryBufferByteAccess dependent We can make it a dependent type on the existing template type parameter T. Also update the test to (1) avoid endianness, (2) let the compiler deal with the magic numbers. --- include/wil/cppwinrt_helpers.h | 10 ++++---- tests/CppWinRTTests.cpp | 42 ++++++++++++++++++---------------- 2 files changed, 28 insertions(+), 24 deletions(-) diff --git a/include/wil/cppwinrt_helpers.h b/include/wil/cppwinrt_helpers.h index b2d4bcaa3..67117247c 100644 --- a/include/wil/cppwinrt_helpers.h +++ b/include/wil/cppwinrt_helpers.h @@ -230,12 +230,14 @@ namespace wil //! By default, returns an array_view, but you can provide an alternate //! type such as to_array_view. //! You must include memorybuffer.h in order to use this overload of to_array_view. - template + template winrt::array_view to_array_view(winrt::Windows::Foundation::IMemoryBufferReference const& reference) { uint8_t* data; uint32_t capacity; - winrt::check_hresult(reference.as>()->GetBuffer(&data, &capacity)); + // Make IMemoryBufferByteAccess a dependent type so we can talk about it even if hasn't been included. + using IMemoryBufferByteAccess = std::enable_if_t, ::Windows::Foundation::IMemoryBufferByteAccess>; + winrt::check_hresult(reference.as()->GetBuffer(&data, &capacity)); return { reinterpret_cast(data), static_cast(capacity / sizeof(T)) }; } @@ -246,10 +248,10 @@ namespace wil //! By default, returns an array_view, but you can provide an alternate //! type such as to_array_view. //! You must include memorybuffer.h in order to use this overload of to_array_view. - template + template winrt::array_view to_array_view(winrt::Windows::Foundation::IMemoryBuffer const& buffer) { - return to_array_view(buffer.CreateReference()); + return to_array_view(buffer.CreateReference()); } } #endif diff --git a/tests/CppWinRTTests.cpp b/tests/CppWinRTTests.cpp index 4e97cc086..fc7368104 100644 --- a/tests/CppWinRTTests.cpp +++ b/tests/CppWinRTTests.cpp @@ -186,37 +186,39 @@ TEST_CASE("CppWinRTTests::VectorToVector", "[cppwinrt]") TEST_CASE("CppWinRTTests::BufferToArrayView", "[cppwinrt]") { - // Create a buffer of capacity 8 and length 4. - auto buffer = winrt::Windows::Storage::Streams::Buffer(8); - buffer.Length(4); - // Get a Capacity-based int view and set the ints to 256 and 512. + std::array testData = { 314159265, 27182818 }; + auto testDataByteStart = reinterpret_cast(testData.data()); + + // Create a buffer with capacity for our testData, length for one of the int32_t's. + auto buffer = winrt::Windows::Storage::Streams::Buffer(sizeof(testData)); + buffer.Length(sizeof(int32_t)); + + // Get a Capacity-based int view and set the test data. { auto view = wil::to_array_view_for_capacity(buffer); - REQUIRE(view.size() == 2); - view[0] = 256; - view[1] = 512; + REQUIRE(view.size() == testData.size()); + std::copy(view.begin(), view.end(), testData.begin()); } - // Get a Length-based byte view and confirm that the four bytes are { 0, 1, 0, 0 }. - // (Assumes little-endian system.) + // Get a Length-based byte view and confirm that the four bytes match the first four + // bytes of our test data. { auto view = wil::to_array_view(buffer); - REQUIRE(view.size() == 4); - REQUIRE(view == winrt::array_view(std::array{ 0, 1, 0, 0 })); + REQUIRE(view.size() == sizeof(int32_t)); + REQUIRE(view == winrt::array_view(testDataByteStart, sizeof(int32_t))); } - // Create an IMemoryBuffer around the Buffer. + // Create an IMemoryBuffer around the Buffer. This uses the Buffer's Capacity as the MemoryBuffer size. auto mbuffer = winrt::Windows::Storage::Streams::Buffer::CreateMemoryBufferOverIBuffer(buffer); - // Verify that the buffer is the 2 ints 256 and 512. + // Verify that the buffer is the test data as int32_t. { - auto view = wil::to_array_view(mbuffer); - REQUIRE(view.size() == 2); - REQUIRE(view == winrt::array_view(std::array{ 256, 512 })); + auto view = wil::to_array_view(mbuffer); + REQUIRE(view.size() == testData.size()); + REQUIRE(view == winrt::array_view(testData)); } - // Verify that the buffer reference is the 8 bytes { 0, 1, 0, 0, 0, 2, 0, 0 }. - // (Assumes little-endian system.) + // Verify that the buffer reference gives us the test data as uint8_t. { auto view = wil::to_array_view(mbuffer.CreateReference()); - REQUIRE(view.size() == 8); - REQUIRE(view == winrt::array_view(std::array{ 0, 1, 0, 0, 0, 2, 0, 0 })); + REQUIRE(view.size() == sizeof(testData)); + REQUIRE(view == winrt::array_view(testDataByteStart, sizeof(testData))); } }