diff --git a/include/wil/safearrays.h b/include/wil/safearrays.h new file mode 100644 index 00000000..544876e6 --- /dev/null +++ b/include/wil/safearrays.h @@ -0,0 +1,1100 @@ +//********************************************************* +// +// Copyright (c) Microsoft. All rights reserved. +// This code is licensed under the MIT License. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +// PARTICULAR PURPOSE AND NONINFRINGEMENT. +// +//********************************************************* + +#ifdef _KERNEL_MODE +#error This header is not supported in kernel-mode. +#endif + +#include // new(std::nothrow) +#include "wistd_type_traits.h" +#include +#include "resource.h" + +namespace wil +{ +#if !defined(__WIL_SAFEARRAY_) +#define __WIL_SAFEARRAY_ + /// @cond + namespace details + { + template struct infer_safearray_type_traits{}; + template<> struct infer_safearray_type_traits { static constexpr auto vartype = VT_I1; }; + //template<> struct infer_safearray_type_traits { static constexpr auto vartype = VT_I2; }; + template<> struct infer_safearray_type_traits { static constexpr auto vartype = VT_I4; }; + template<> struct infer_safearray_type_traits { static constexpr auto vartype = VT_I4; }; + template<> struct infer_safearray_type_traits { static constexpr auto vartype = VT_I8; }; + template<> struct infer_safearray_type_traits { static constexpr auto vartype = VT_UI1; }; + template<> struct infer_safearray_type_traits { static constexpr auto vartype = VT_UI2; }; + template<> struct infer_safearray_type_traits { static constexpr auto vartype = VT_UI4; }; + template<> struct infer_safearray_type_traits { static constexpr auto vartype = VT_UI4; }; + template<> struct infer_safearray_type_traits { static constexpr auto vartype = VT_UI8; }; + template<> struct infer_safearray_type_traits { static constexpr auto vartype = VT_R4; }; + //template<> struct infer_safearray_type_traits { static constexpr auto vartype = VT_R8; }; + template<> struct infer_safearray_type_traits { static constexpr auto vartype = VT_BOOL; }; + template<> struct infer_safearray_type_traits { static constexpr auto vartype = VT_DATE; }; + template<> struct infer_safearray_type_traits { static constexpr auto vartype = VT_CY; }; + template<> struct infer_safearray_type_traits { static constexpr auto vartype = VT_DECIMAL; }; + template<> struct infer_safearray_type_traits { static constexpr auto vartype = VT_BSTR; }; + template<> struct infer_safearray_type_traits { static constexpr auto vartype = VT_UNKNOWN; }; + template<> struct infer_safearray_type_traits { static constexpr auto vartype = VT_DISPATCH; }; + template<> struct infer_safearray_type_traits { static constexpr auto vartype = VT_VARIANT; }; + + inline void __stdcall SafeArrayDestroy(SAFEARRAY* psa) WI_NOEXCEPT + { + FAIL_FAST_IF_FAILED(::SafeArrayDestroy(psa)); + } + + inline void __stdcall SafeArrayLock(SAFEARRAY* psa) WI_NOEXCEPT + { + FAIL_FAST_IF_FAILED(::SafeArrayLock(psa)); + } + + inline void __stdcall SafeArrayUnlock(SAFEARRAY* psa) WI_NOEXCEPT + { + FAIL_FAST_IF_FAILED(::SafeArrayUnlock(psa)); + } + + template + inline void __stdcall SafeArrayAccessData(SAFEARRAY* psa, T*& p) WI_NOEXCEPT + { + WI_ASSERT(sizeof(T) == ::SafeArrayGetElemsize(psa)); + FAIL_FAST_IF_FAILED(::SafeArrayAccessData(psa, reinterpret_cast(&p))); + } + + inline void __stdcall SafeArrayUnaccessData(SAFEARRAY* psa) WI_NOEXCEPT + { + FAIL_FAST_IF_FAILED(::SafeArrayUnaccessData(psa)); + } + + inline VARTYPE __stdcall SafeArrayGetVartype(SAFEARRAY* psa) WI_NOEXCEPT + { + VARTYPE vt = VT_NULL; // Invalid for SAs, so use to mean SA was null + if ((psa != nullptr) && FAILED(::SafeArrayGetVartype(psa, &vt))) + { + vt = VT_EMPTY; // Invalid for SAs, use to mean type couldn't be determined + } + return vt; + } + + inline ULONG __stdcall SafeArrayGetLockCount(SAFEARRAY* psa) WI_NOEXCEPT + { + return (psa != nullptr) ? psa->cLocks : 0U; + } + + inline HRESULT __stdcall SafeArrayCreate(VARTYPE vt, UINT dims, SAFEARRAYBOUND* sab, SAFEARRAY*& psa) WI_NOEXCEPT + { + WI_ASSERT(dims > 0); + psa = ::SafeArrayCreate(vt, dims, sab); + RETURN_LAST_ERROR_IF_NULL(psa); + WI_ASSERT(vt == details::SafeArrayGetVartype(psa)); + return S_OK; + } + + inline HRESULT __stdcall SafeArrayGetDimSize(SAFEARRAY* psa, UINT dims, ULONG* result) WI_NOEXCEPT + { + RETURN_HR_IF(E_INVALIDARG, ((dims == 0) || (dims > psa->cDims))); + // Read from the back of the array because SAFEARRAYs are stored with the dimensions in reverse order + *result = psa->rgsabound[psa->cDims - dims].cElements; + return S_OK; + } + + inline HRESULT __stdcall SafeArrayGetTotalSize(SAFEARRAY* psa, ULONG* result) WI_NOEXCEPT + { + if (psa != nullptr) + { + ULONGLONG elements = 1; + for (UINT i = 0; i < psa->cDims; ++i) + { + // Determine the total size of the safearray by multiplying + // the sizes of each dimension together + elements *= psa->rgsabound[i].cElements; + if (elements > ULONG_MAX) + { + RETURN_HR(HRESULT_FROM_WIN32(ERROR_ARITHMETIC_OVERFLOW)); + } + } + *result = static_cast(elements); + } + else + { + // If it's invalid, it doesn't contain any elements + *result = 0; + } + return S_OK; + } + + typedef resource_policy safearray_resource_policy; + } + /// @endcond + + typedef unique_any safearray_unlock_scope_exit; + typedef unique_any safearray_unaccess_scope_exit; + + //! Guarantees a SafeArrayUnlock call on the given object when the returned object goes out of scope + //! Note: call SafeArrayUnlock early with the reset() method on the returned object or abort the call with the release() method + WI_NODISCARD inline safearray_unlock_scope_exit SafeArrayUnlock_scope_exit(SAFEARRAY* psa) WI_NOEXCEPT + { + details::SafeArrayLock(psa); + return safearray_unlock_scope_exit(psa); + } + + //! Guarantees a SafeArrayUnaccessData call on the given object when the returned object goes out of scope + //! Note: call SafeArrayUnaccessData early with the reset() method on the returned object or abort the call with the release() method + template + WI_NODISCARD inline safearray_unaccess_scope_exit SafeArrayAccessData_scope_exit(SAFEARRAY* psa, T*& p) WI_NOEXCEPT + { + details::SafeArrayAccessData(psa, p); + return safearray_unaccess_scope_exit(psa); + } + + //! Class that facilitates the direct access to the contents of a SAFEARRAY and "unaccessing" it when done + //! It enables treating the safearray like an regular array and/or performing a ranged-for on the contents. + //! Accessing a safearray increases its lock count, so attempts to destroy the safearray will fail until it is unaccessed. + //! This class works even if the SAFEARRAY is multi-dimensional by treating it as a large single-dimension array, + //! but doesn't support access via a multi-dimensional index. + //! NOTE: This class does not manage the lifetime of the SAFEARRAY itself. See @ref safearray_t. + //! ~~~~ + //! HRESULT copy_to_bstr_vector(SAFEARRAY* psa, std::vector& result) + //! { + //! auto data = wil::safearraydata_nothrow{}; + //! RETURN_IF_FAILED(data.access(psa)); + //! result.reserve(result.size() + data.size()); + //! for (BSTR& bstr : data) + //! { + //! result.emplace_back(wil::make_bstr_nothrow(bstr)); + //! RETURN_HR_IF_NULL(E_OUTOFMEMORY, result.rbegin()->get()); + //! } + //! return S_OK; + //! } + //! ~~~~ + //! wil::unique_bstr_safearray move_to_safearray(std::vector& source) + //! { + //! auto sa = wil::unique_bstr_safearray{source.size()}; + //! auto current = std::begin(source); + //! for (BSTR& bstr : sa.access_data()) + //! { + //! // Transfer ownership of the BSTR into the safearray + //! bstr = (*current++).release(); + //! } + //! return sa; + //! } + //! ~~~~ + //! void handle_safearray_of_bools(SAFEARRAY* psa, std::function fnHandler) + //! { + //! auto data = wil::safearraydata{psa}; + //! for (auto i = 0U; i < data.size(); ++i) + //! { + //! // Translate to Regular Bool and Invoke the Handler + //! fnHandler(data[i] != VARIANT_FALSE, i); + //! } + //! } + //! ~~~~ + //! template + //! std::vector extract_all_values(SAFEARRAY* psa) + //! { + //! auto data = wil::safearraydata{psa}; + //! // Construct the vector from the iterators + //! return {data.begin(), data.end()}; + //! } + //! ~~~~ + //! template + //! std::vector> extract_all_interfaces(SAFEARRAY* psa) + //! { + //! auto result = std::vector>{}; + //! auto data = wil::safearraydata{psa}; + //! result.reserve(data.size()); + //! for (auto& p : data) // Type of p is LPUNKNOWN + //! { + //! // Use "tag_com_query" if you want failure instead of nullptr if INTERFACE not supported + //! result.emplace_back(p, wil::details::tag_try_com_query); + //! } + //! return result; + //! } + //! ~~~~ + //! @tparam T The underlying datatype expected in the SAFEARRAY. It should be a scalar type, BSTR, VARIANT_BOOL, LPUNKNOWN/LPDISPATCH, etc. + //! @tparam err_policy Represents the error policy for the class (error codes, exceptions, or fail fast; see @ref page_errors) + template + class safearraydata_t + { + public: + typedef typename err_policy::result result; + typedef safearray_unaccess_scope_exit::pointer pointer; + typedef T value_type; + typedef value_type* value_pointer; + typedef const value_type* const_value_pointer; + typedef value_type& value_reference; + typedef const value_type& const_value_reference; + typedef value_type* iterator; + typedef const value_type* const_iterator; + + public: + // Exception-based construction + explicit safearraydata_t(pointer psa) + { + static_assert(wistd::is_same::value, "this constructor requires exceptions; use the create method"); + access(psa); + } + safearraydata_t() = default; + + result access(pointer psa) + { + return err_policy::HResult([&]() + { + m_unaccess = SafeArrayAccessData_scope_exit(psa, m_begin); + RETURN_IF_FAILED(details::SafeArrayGetTotalSize(m_unaccess.get(), &m_size)); + return S_OK; + }()); + } + + // Ranged-for style + iterator begin() WI_NOEXCEPT { WI_ASSERT(m_begin != nullptr); return m_begin; } + iterator end() WI_NOEXCEPT { WI_ASSERT(m_begin != nullptr); return m_begin+m_size; } + const_iterator begin() const WI_NOEXCEPT { WI_ASSERT(m_begin != nullptr); return m_begin; } + const_iterator end() const WI_NOEXCEPT { WI_ASSERT(m_begin != nullptr); return m_begin+m_size; } + + // Old style iteration + ULONG size() const WI_NOEXCEPT { return m_size; } + WI_NODISCARD value_reference operator[](ULONG i) { WI_ASSERT(i < m_size); return *(m_begin + i); } + WI_NODISCARD const_value_reference operator[](ULONG i) const { WI_ASSERT(i < m_size); return *(m_begin +i); } + + // Utilities + bool empty() const WI_NOEXCEPT { return m_size == 0UL; } + + private: + safearray_unaccess_scope_exit m_unaccess{}; + value_pointer m_begin{}; + ULONG m_size{}; + }; + + template + using safearraydata_nothrow = safearraydata_t; + template + using safearraydata_failfast = safearraydata_t; + +#ifdef WIL_ENABLE_EXCEPTIONS + template + using safearraydata = safearraydata_t; +#endif + + //! RAII class for SAFEARRAYs that lives between unique_any_t and unique_storage to provide extra SAFEARRAY functionality. + //! SAFEARRAYs provide a convenient way of passing an array of values across APIs and can be useful because they will clean + //! up their resources (interface ref counts, BSTRs, etc.) when properly destroyed. If you have a SAFEARRAY of interface + //! pointers or BSTRs, there is no need to call Release or SysFreeString on each element (that is done automatically + //! when the array is destroyed in SafeArrayDestroy), so the only resource that needs to be managed is the SAFEARRAY + //! itself. + //! ~~~~ + //! // Return a SAFEARRAY from an API + //! HRESULT GetWonderfulData(SAFEARRAY** ppsa) + //! { + //! wil::unique_bstr_safearray_nothrow sa{}; + //! RETURN_IF_FAILED(sa.create(32)); + //! { + //! wil::safearraydata_nothrow data{}; + //! RETURN_IF_FAILED(data.access(sa.get())); + //! for (auto& bstr : data) + //! { + //! // SAFEARRAY will own this string and clean it up + //! bstr = ::SysAllocString(L"Wonderful!"); + //! // Even if we bail early, nothing will leak + //! RETURN_HR_IF_NULL(E_OUTOFMEMORY, bstr); + //! } + //! } + //! *ppsa = sa.release(); + //! return S_OK; + //! } + //! ~~~~ + //! // Obtain a SAFEARRAY from an API + //! HRESULT ProcessWonderful() + //! { + //! wil::unique_safearray sa{}; + //! + //! // Invoke the API + //! RETURN_IF_FAILED(::GetWonderfulData(sa.put())); + //! // Verify the output is expected + //! RETURN_HR_IF(E_UNEXPECTED, sa.vartype() != VT_BSTR); + //! for (auto& bstr : sa.access_data()) + //! { + //! // Use the BSTR, no clean up necessary + //! DoSomethingWithBSTR(bstr); + //! } + //! return S_OK; + //! } + //! ~~~~ + //! @tparam storage_t Storage class that manages the SAFEARRAY pointer + //! @tparam err_policy Represents the error policy for the class (error codes, exceptions, or fail fast; see @ref page_errors) + //! @tparam element_t The underlying datatype that is contained in the safearray OR + //! it can be void to make the safearray generic (and not as typesafe) + template + class safearray_t : public storage_t + { + private: + // SFINAE Helpers to improve readability + template + using is_interface_type = wistd::disjunction, + wistd::is_same>; + template + using is_pointer_type = wistd::disjunction, + wistd::is_same, + wistd::is_same>; + + template + using is_type_not_set = wistd::is_same; + template + using is_type_set = wistd::negation>; + template + using is_value_type = wistd::conjunction>, is_type_set>; + + public: + template + using safearraydata_t = safearraydata_t; + + // Represents this type + using unique_type = unique_any_t>; + + typedef typename err_policy::result result; + typedef typename storage_t::pointer pointer; + typedef element_t elemtype; + + public: + // forward all base class constructors... + template + explicit safearray_t(args_t&&... args) WI_NOEXCEPT : storage_t(wistd::forward(args)...) {} + + // Exception-based construction + template::value, int>::type = 0> + safearray_t(VARTYPE vt, UINT elements, LONG lowerBound = 0) + { + static_assert(wistd::is_same::value, "this constructor requires exceptions; use the create method"); + create(vt, elements, lowerBound); + } + template::value, int>::type = 0> + explicit safearray_t(UINT elements, LONG lowerBound = 0) + { + static_assert(wistd::is_same::value, "this constructor requires exceptions; use the create method"); + create(elements, lowerBound); + } + + // Low-level, arbitrary number of dimensions + // Uses Explicit Type + template::value, int>::type = 0> + result create(VARTYPE vt, UINT dims, SAFEARRAYBOUND* sab) + { + return err_policy::HResult(_create(vt, dims, sab)); + } + // Uses Inferred Type + template::value, int>::type = 0> + result create(UINT dims, SAFEARRAYBOUND* sab) + { + constexpr auto vt = static_cast(details::infer_safearray_type_traits::vartype); + return err_policy::HResult(_create(vt, dims, sab)); + } + + // Single Dimension Specialization + // Uses Explicit Type + template::value, int>::type = 0> + result create(VARTYPE vt, UINT elements, LONG lowerBound = 0) + { + auto bounds = SAFEARRAYBOUND{ elements, lowerBound }; + return err_policy::HResult(_create(vt, 1, &bounds)); + } + // Uses Inferred Type + template::value, int>::type = 0> + result create(UINT elements, LONG lowerBound = 0) + { + constexpr auto vt = static_cast(details::infer_safearray_type_traits::vartype); + auto bounds = SAFEARRAYBOUND{ elements, lowerBound }; + return err_policy::HResult(_create(vt, 1, &bounds)); + } + + //! Create a Copy + //! Replaces the current SAFEARRAY (if any) with a new one that is created by duplicating the elements in + //! the SAFEARRAY* provided as a parameter in psaSource + result create_copy(pointer psaSource) + { + WI_ASSERT(psaSource != nullptr); + return err_policy::HResult([&]() + { + auto psa = storage_t::policy::invalid_value(); + RETURN_IF_FAILED(::SafeArrayCopy(psaSource, &psa)); + storage_t::reset(psa); + return S_OK; + }()); + } + + //! @name Property Helpers + //! Functions to determine the size and shape of the SAFEARRAY, which + //! can have an arbitrary number of dimensions and each dimension can + //! it's own size and boundaries. + //! @{ + + //! Indicates the number of dimensions in the SAFEARRAY. The 1st Dimension + //! is always 1, and there never is a Dimension 0. + ULONG dims() const WI_NOEXCEPT { return ::SafeArrayGetDim(storage_t::get()); } + //! Returns the lowest possible index for the given dimension. + result lbound(UINT dim, LONG* result) const + { + WI_ASSERT((dim > 0) && (dim <= dims())); + return err_policy::HResult(::SafeArrayGetLBound(storage_t::get(), dim, result)); + } + //! Returns the highest possible index for the given dimension. + result ubound(UINT dim, LONG* result) const + { + WI_ASSERT((dim > 0) && (dim <= dims())); + return err_policy::HResult(::SafeArrayGetUBound(storage_t::get(), dim, result)); + } + //! 1D Specialization of lbound + result lbound(LONG* result) const + { + WI_ASSERT(dims() == 1); + return err_policy::HResult(::SafeArrayGetLBound(storage_t::get(), 1, result)); + } + //! 1D Specialization of ubound + result ubound(LONG* result) const + { + WI_ASSERT(dims() == 1); + return err_policy::HResult(::SafeArrayGetUBound(storage_t::get(), 1, result)); + } + //! Indicates the total number of elements across all the dimensions + result size(ULONG* result) const + { + return err_policy::HResult(details::SafeArrayGetTotalSize(storage_t::get(), result)); + } + //! Returns the number of elements in the specified dimension + //! Same as, but faster than, the typical ubound() - lbound() + 1 + result dim_size(UINT dim, ULONG* result) const + { + WI_ASSERT((dim > 0) && (dim <= dims())); + return err_policy::HResult(details::SafeArrayGetDimSize(storage_t::get(), dim, result)); + } + //! Return the size, in bytes, of each element in the array + ULONG elemsize() const WI_NOEXCEPT { return ::SafeArrayGetElemsize(storage_t::get()); } + + //! Retrieves the stored type (when not working with a fixed type) + template::value, int>::type = 0> + VARTYPE vartype() WI_NOEXCEPT + { + return details::SafeArrayGetVartype(storage_t::get()); + } + + //! Returns the current number of locks on the SAFEARRAY + ULONG lock_count() const WI_NOEXCEPT + { + return details::SafeArrayGetLockCount(storage_t::get()); + } + //! @} + + //! Returns an object that keeps a lock count on the safearray until it goes out of scope + //! Use to keep the SAFEARRAY from being destroyed but without accessing the contents + //! Not needed when using safearraydata_t because accessing the data also holds a lock + //! ~~~~ + //! void Process(wil::unique_safearray& sa) + //! { + //! auto lock = sa.scope_lock(); + //! LongRunningFunction(sa); + //! } + WI_NODISCARD safearray_unlock_scope_exit scope_lock() const WI_NOEXCEPT + { + return wil::SafeArrayUnlock_scope_exit(storage_t::get()); + } + + //! @name Element Access + //! Instead of using the safearraydata_t to access the entire collection of elements + //! at once, it is also possible to access them one at a time. However, using this + //! functionality is less efficient because it deals with copies of the elements + //! that the caller is then responsible for cleaning up, so use RAII data types + //! for items that require clean up, like BSTR, VARIANT, or LPUNKNOWN + //! ~~~~ + //! HRESULT ProcessWonderful_UsingElements() + //! { + //! wil::unique_safearray_nothrow sa{}; + //! wil::unique_bstr bstr{}; + //! ULONG size{}; + //! + //! // Invoke the API + //! RETURN_IF_FAILED(::GetWonderfulData(sa.put())); + //! // Verify the output is expected + //! RETURN_HR_IF(E_UNEXPECTED, sa.vartype() != VT_BSTR); + //! RETURN_HR_IF(E_UNEXPECTED, sa.dims() != 1); + //! RETURN_IF_FAILED(sa.size(&size)); + //! for (auto i = 0U; i < size; ++i) + //! { + //! // Copy from the safearray to the unique_bstr + //! sa.get_element(i, bstr.put()); // Will release current BSTR + //! DoSomethingWithBSTR(bstr); + //! } + //! return S_OK; + //! } + //! ~~~~ + //! @{ + + //! Copies the specified element and puts it into the specified index in the SAFEARRAY. + //! The SAFEARRAY will own the copy and will clean up the resources when it is destroyed. + //! HOWEVER, the caller still owns the original and is responsible for it's clean up + template::value, int>::type = 0> + result put_element(LONG index, const T& val) + { + WI_ASSERT(sizeof(val) == elemsize()); + WI_ASSERT(dims() == 1); + return err_policy::HResult(::SafeArrayPutElement(storage_t::get(), &index, reinterpret_cast(const_cast(&val)))); + } + template::value, int>::type = 0> + result put_element(LONG index, T val) + { + WI_ASSERT(sizeof(val) == elemsize()); + WI_ASSERT(dims() == 1); + return err_policy::HResult(::SafeArrayPutElement(storage_t::get(), &index, val)); + } + //! Retrieves a copy of the element at the specified index. The caller owns this copy and must free + //! any resources with LPUNKNOWN->Release, SysFreeString, VariantClear, etc. + template::value, int>::type = 0> + result get_element(LONG index, T& val) + { + WI_ASSERT(sizeof(val) == elemsize()); + WI_ASSERT(dims() == 1); + return err_policy::HResult(::SafeArrayGetElement(storage_t::get(), &index, &val)); + } + + //! Multiple Dimension version of put_element + template::value, int>::type = 0> + result put_element(LONG* indices, const T& val) + { + WI_ASSERT(sizeof(val) == elemsize()); + return err_policy::HResult(::SafeArrayPutElement(storage_t::get(), indices, reinterpret_cast(const_cast(&val)))); + } + template::value, int>::type = 0> + result put_element(LONG* indices, T val) + { + WI_ASSERT(sizeof(val) == elemsize()); + return err_policy::HResult(::SafeArrayPutElement(storage_t::get(), indices, val)); + } + //! Multiple Dimension version of get_element + template::value, int>::type = 0> + result get_element(LONG* indices, T& val) + { + WI_ASSERT(sizeof(val) == elemsize()); + return err_policy::HResult(::SafeArrayGetElement(storage_t::get(), indices, &val)); + } + + //! Lowest-Level, Type-Erased versions for those who know what they're doing. + result put_element(LONG index, void* pv) + { + WI_ASSERT(dims() == 1); + return err_policy::HResult(::SafeArrayPutElement(storage_t::get(), &index, pv)); + } + result get_element(LONG index, void* pv) + { + WI_ASSERT(dims() == 1); + return err_policy::HResult(::SafeArrayGetElement(storage_t::get(), &index, pv)); + } + result put_element(LONG* indices, void* pv) + { + return err_policy::HResult(::SafeArrayPutElement(storage_t::get(), indices, pv)); + } + result get_element(LONG* indices, void* pv) + { + return err_policy::HResult(::SafeArrayGetElement(storage_t::get(), indices, pv)); + } + + //! Safer versions "get_element/put_element" that returns the copy wrapped in the appropriate storage class +#if defined(__WIL_OLEAUTO_H_) + template::value, int>::type = 0> + result get_element(LONG index, wil::unique_bstr& val) + { + WI_ASSERT(sizeof(BSTR) == elemsize()); + WI_ASSERT(dims() == 1); + return err_policy::HResult(::SafeArrayGetElement(storage_t::get(), &index, val.put())); + } + template::value, int>::type = 0> + result put_element(LONG index, wil::unique_bstr& val) + { + WI_ASSERT(sizeof(BSTR) == elemsize()); + WI_ASSERT(dims() == 1); + return err_policy::HResult(::SafeArrayPutElement(storage_t::get(), &index, val.get())); + } + + template::value, int>::type = 0> + result get_element(LONG index, wil::unique_variant& val) + { + WI_ASSERT(sizeof(VARIANT) == elemsize()); + WI_ASSERT(dims() == 1); + return err_policy::HResult(::SafeArrayGetElement(storage_t::get(), &index, val.reset_and_addressof())); + } + template::value, int>::type = 0> + result put_element(LONG index, wil::unique_variant& val) + { + WI_ASSERT(sizeof(VARIANT) == elemsize()); + WI_ASSERT(dims() == 1); + return err_policy::HResult(::SafeArrayPutElement(storage_t::get(), &index, val.addressof())); + } + + //! Multidimensional Versions + template::value, int>::type = 0> + result get_element(LONG* indices, wil::unique_bstr& val) + { + WI_ASSERT(sizeof(BSTR) == elemsize()); + return err_policy::HResult(::SafeArrayGetElement(storage_t::get(), indices, val.put())); + } + template::value, int>::type = 0> + result put_element(LONG* indices, wil::unique_bstr& val) + { + WI_ASSERT(sizeof(BSTR) == elemsize()); + return err_policy::HResult(::SafeArrayPutElement(storage_t::get(), indices, val.get())); + } + + template::value, int>::type = 0> + result get_element(LONG* indices, wil::unique_variant& val) + { + WI_ASSERT(sizeof(VARIANT) == elemsize()); + return err_policy::HResult(::SafeArrayGetElement(storage_t::get(), indices, val.reset_and_addressof())); + } + template::value, int>::type = 0> + result put_element(LONG* indices, wil::unique_variant& val) + { + WI_ASSERT(sizeof(VARIANT) == elemsize()); + return err_policy::HResult(::SafeArrayPutElement(storage_t::get(), indices, val.addressof())); + } +#endif + +#if defined(__WIL_COM_INCLUDED) // Only leverage the COM functionality if it's been included + template::value, int>::type = 0> + result get_element(LONG index, wil::com_ptr_t& val) + { + WI_ASSERT(sizeof(T) == elemsize()); + WI_ASSERT(dims() == 1); + return err_policy::HResult([&]() + { + wil::com_ptr_nothrow unk{}; + RETURN_IF_FAILED(::SafeArrayGetElement(storage_t::get(), &index, unk.put())); + RETURN_IF_FAILED(unk.query_to(val.put())); + return S_OK; + }()); + } + template::value, int>::type = 0> + result put_element(LONG index, wil::com_ptr_t& val) + { + WI_ASSERT(sizeof(T) == elemsize()); + WI_ASSERT(dims() == 1); + return err_policy::HResult(::SafeArrayPutElement(storage_t::get(), &index, val.get())); + } + + template::value, int>::type = 0> + result get_element(LONG* indices, wil::com_ptr_t& val) + { + WI_ASSERT(sizeof(T) == elemsize()); + return err_policy::HResult([&]() + { + wil::com_ptr_nothrow unk{}; + RETURN_IF_FAILED(::SafeArrayGetElement(storage_t::get(), indices, unk.put())); + RETURN_IF_FAILED(unk.query_to(val.put())); + return S_OK; + }()); + } + template::value, int>::type = 0> + result put_element(LONG* indices, wil::com_ptr_t& val) + { + WI_ASSERT(sizeof(T) == elemsize()); + return err_policy::HResult(::SafeArrayPutElement(storage_t::get(), indices, val.get())); + } +#endif + //! @} + + //! Non-error code based helper functions that are easier to read + //! @{ + + //! Returns the lower bound of the given dimension + WI_NODISCARD LONG lbound(UINT dim = 1) const + { + static_assert(wistd::is_same::value, "this method requires exceptions or failfast"); + LONG result = 0; + lbound(dim, &result); + return result; + } + //! Returns the bupper bound of the given dimension + WI_NODISCARD LONG ubound(UINT dim = 1) const + { + static_assert(wistd::is_same::value, "this method requires exceptions or failfast"); + LONG result = 0; + ubound(dim, &result); + return result; + } + //! Return the total number of elements in the SAFEARRAY + WI_NODISCARD ULONG size() const + { + static_assert(wistd::is_same::value, "this method requires exceptions or failfast"); + ULONG result = 0; + size(&result); + return result; + } + //! Returns the number of elements in the given dimension + WI_NODISCARD ULONG dim_size(UINT dim = 1) const + { + static_assert(wistd::is_same::value, "this method requires exceptions or failfast"); + ULONG result = 0; + dim_size(dim, &result); + return result; + } + //! Returns a copy of the SAFEARRAY, including all individual elements + WI_NODISCARD unique_type create_copy() const + { + static_assert(wistd::is_same::value, "this method requires exceptions or failfast"); + + auto result = unique_type{}; + result.create_copy(storage_t::get()); + return result; + } + + //! Returns a safearraydata_t that provides direct access to this SAFEARRAY's contents. See @ref safearraydata_t. + //! ~~~~ + //! // Create a new safearray by copying a vector of wil::unique_bstr + //! wil::unique_bstr_safearray copy_to_safearray(std::vector& source) + //! { + //! auto sa = wil::unique_bstr_safearray{source.size()}; + //! auto current = std::begin(source); + //! for (BSTR& bstr : sa.access_data()) + //! { + //! // Create a copy for the safearray to own + //! bstr = ::SysAllocString((current++)->get()); + //! } + //! return sa; + //! } + //! ~~~~ + template::value, int>::type = 0> + WI_NODISCARD safearraydata_t access_data() const + { + static_assert(wistd::is_same::value, "this method requires exceptions or failfast"); + return safearraydata_t{ storage_t::get() }; + } + + //! Special Get Functions + //! ~~~~ + //! // Create a new safearray by copying a vector of wil::unique_bstr + //! std::vector copy_safearray_to_vector_and_destroy(SAFEARRAY*& psa) + //! { + //! auto sa = wil::unique_bstr_safearray{}; + //! sa.reset(psa); // The SAFEARRAY is now owned by the instance of unique_safearray + //! psa = nullptr; // Signal to the caller the SA was cleaned up + //! + //! THROW_HR_IF(E_UNEXPECTED, sa.vartype() != VT_BSTR); + //! + //! auto result = std::vector{}; + //! result.reserve(sa.size()); + //! for (auto i = 0; i < sa.size(); ++i) + //! { + //! result.emplace_back(sa.get_element(i)); + //! } + //! return result; + //! } + //! ~~~~ +#if defined(__WIL_OLEAUTO_H_) + template::value, int>::type = 0> + WI_NODISCARD auto get_element(LONG index) -> wil::unique_bstr + { + static_assert(wistd::is_same::value, "this method requires exceptions or failfast"); + wil::unique_bstr result{}; + get_element(index, result); + return result; + } + template::value, int>::type = 0> + WI_NODISCARD auto get_element(LONG index) -> wil::unique_variant + { + static_assert(wistd::is_same::value, "this method requires exceptions or failfast"); + wil::unique_variant result{}; + get_element(index, result); + return result; + } + + template::value, int>::type = 0> + WI_NODISCARD auto get_element(LONG* indices) -> wil::unique_bstr + { + static_assert(wistd::is_same::value, "this method requires exceptions or failfast"); + wil::unique_bstr result{}; + get_element(indices, result); + return result; + } + template::value, int>::type = 0> + WI_NODISCARD auto get_element(LONG* indices) -> wil::unique_variant + { + static_assert(wistd::is_same::value, "this method requires exceptions or failfast"); + wil::unique_variant result{}; + get_element(indices, result); + return result; + } +#endif + +#if defined(__WIL_COM_INCLUDED) + template::value, int>::type = 0> + WI_NODISCARD auto get_element(LONG index) -> wil::com_ptr_t + { + static_assert(wistd::is_same::value, "this method requires exceptions or failfast"); + wil::com_ptr_t result{}; + get_element(index, result); + return result; + } + template::value, int>::type = 0> + WI_NODISCARD auto get_element(LONG index) -> wil::com_ptr_t + { + static_assert(wistd::is_same::value, "this method requires exceptions or failfast"); + wil::com_ptr_t result{}; + get_element(index, result); + return result; + } + template::value, int>::type = 0> + WI_NODISCARD auto get_element(LONG* indices) -> wil::com_ptr_t + { + static_assert(wistd::is_same::value, "this method requires exceptions or failfast"); + wil::com_ptr_t result{}; + get_element(indices, result); + return result; + } + template::value, int>::type = 0> + WI_NODISCARD auto get_element(LONG* indices) -> wil::com_ptr_t + { + static_assert(wistd::is_same::value, "this method requires exceptions or failfast"); + wil::com_ptr_t result{}; + get_element(indices, result); + return result; + } +#endif + //! @} + + private: + HRESULT _create(VARTYPE vt, UINT dims, SAFEARRAYBOUND* sab) + { + auto psa = storage_t::policy::invalid_value(); + RETURN_IF_FAILED(details::SafeArrayCreate(vt, dims, sab, psa)); + storage_t::reset(psa); + return S_OK; + } + }; + + template + using unique_safearray_nothrow_t = unique_any_t, err_returncode_policy, T>>; + template + using unique_safearray_failfast_t = unique_any_t, err_failfast_policy, T>>; + + using unique_safearray_nothrow = unique_any_t, err_returncode_policy, void>>; + using unique_safearray_failfast = unique_any_t, err_failfast_policy, void>>; + using unique_char_safearray_nothrow = unique_any_t, err_returncode_policy, char>>; + using unique_char_safearray_failfast = unique_any_t, err_failfast_policy, char>>; + //using unique_short_safearray_nothrow = unique_any_t, err_returncode_policy, short>>; + //using unique_short_safearray_failfast = unique_any_t, err_failfast_policy, short>>; + using unique_long_safearray_nothrow = unique_any_t, err_returncode_policy, long>>; + using unique_long_safearray_failfast = unique_any_t, err_failfast_policy, long>>; + using unique_int_safearray_nothrow = unique_any_t, err_returncode_policy, int>>; + using unique_int_safearray_failfast = unique_any_t, err_failfast_policy, int>>; + using unique_longlong_safearray_nothrow = unique_any_t, err_returncode_policy, long long>>; + using unique_longlong_safearray_failfast = unique_any_t, err_failfast_policy, long long>>; + using unique_byte_safearray_nothrow = unique_any_t, err_returncode_policy, byte>>; + using unique_byte_safearray_failfast = unique_any_t, err_failfast_policy, byte>>; + using unique_word_safearray_nothrow = unique_any_t, err_returncode_policy, WORD>>; + using unique_word_safearray_failfast = unique_any_t, err_failfast_policy, WORD>>; + using unique_dword_safearray_nothrow = unique_any_t, err_returncode_policy, DWORD>>; + using unique_dword_safearray_failfast = unique_any_t, err_failfast_policy, DWORD>>; + using unique_ulonglong_safearray_nothrow = unique_any_t, err_returncode_policy, ULONGLONG>>; + using unique_ulonglong_safearray_failfast = unique_any_t, err_failfast_policy, ULONGLONG>>; + using unique_float_safearray_nothrow = unique_any_t, err_returncode_policy, float>>; + using unique_float_safearray_failfast = unique_any_t, err_failfast_policy, float>>; + //using unique_double_safearray_nothrow = unique_any_t, err_returncode_policy, double>>; + //using unique_double_safearray_failfast = unique_any_t, err_failfast_policy, double>>; + using unique_varbool_safearray_nothrow = unique_any_t, err_returncode_policy, VARIANT_BOOL>>; + using unique_varbool_safearray_failfast = unique_any_t, err_failfast_policy, VARIANT_BOOL>>; + using unique_date_safearray_nothrow = unique_any_t, err_returncode_policy, DATE>>; + using unique_date_safearray_failfast = unique_any_t, err_failfast_policy, DATE>>; + using unique_currency_safearray_nothrow = unique_any_t, err_returncode_policy, CURRENCY>>; + using unique_currency_safearray_failfast = unique_any_t, err_failfast_policy, CURRENCY>>; + using unique_decimal_safearray_nothrow = unique_any_t, err_returncode_policy, DECIMAL>>; + using unique_decimal_safearray_failfast = unique_any_t, err_failfast_policy, DECIMAL>>; + using unique_bstr_safearray_nothrow = unique_any_t, err_returncode_policy, BSTR>>; + using unique_bstr_safearray_failfast = unique_any_t, err_failfast_policy, BSTR>>; + using unique_unknown_safearray_nothrow = unique_any_t, err_returncode_policy, LPUNKNOWN>>; + using unique_unknown_safearray_failfast = unique_any_t, err_failfast_policy, LPUNKNOWN>>; + using unique_dispatch_safearray_nothrow = unique_any_t, err_returncode_policy, LPDISPATCH>>; + using unique_dispatch_safearray_failfast = unique_any_t, err_failfast_policy, LPDISPATCH>>; + using unique_variant_safearray_nothrow = unique_any_t, err_returncode_policy, VARIANT>>; + using unique_variant_safearray_failfast = unique_any_t, err_failfast_policy, VARIANT>>; + +#ifdef WIL_ENABLE_EXCEPTIONS + template + using unique_safearray_t = unique_any_t, err_exception_policy, T>>; + + using unique_safearray = unique_any_t, err_exception_policy, void>>; + using unique_char_safearray = unique_any_t, err_exception_policy, char>>; + //using unique_short_safearray = unique_any_t, err_exception_policy, short>>; + using unique_long_safearray = unique_any_t, err_exception_policy, long>>; + using unique_int_safearray = unique_any_t, err_exception_policy, int>>; + using unique_longlong_safearray = unique_any_t, err_exception_policy, long long>>; + using unique_byte_safearray = unique_any_t, err_exception_policy, byte>>; + using unique_word_safearray = unique_any_t, err_exception_policy, WORD>>; + using unique_dword_safearray = unique_any_t, err_exception_policy, DWORD>>; + using unique_ulonglong_safearray = unique_any_t, err_exception_policy, ULONGLONG>>; + using unique_float_safearray = unique_any_t, err_exception_policy, float>>; + //using unique_double_safearray = unique_any_t, err_exception_policy, double>>; + using unique_varbool_safearray = unique_any_t, err_exception_policy, VARIANT_BOOL>>; + using unique_date_safearray = unique_any_t, err_exception_policy, DATE>>; + using unique_currency_safearray = unique_any_t, err_exception_policy, CURRENCY>>; + using unique_decimal_safearray = unique_any_t, err_exception_policy, DECIMAL>>; + using unique_bstr_safearray = unique_any_t, err_exception_policy, BSTR>>; + using unique_unknown_safearray = unique_any_t, err_exception_policy, LPUNKNOWN>>; + using unique_dispatch_safearray = unique_any_t, err_exception_policy, LPDISPATCH>>; + using unique_variant_safearray = unique_any_t, err_exception_policy, VARIANT>>; +#endif + +#endif // #if !defined(__WIL_SAFEARRAY_) + +#if defined(__WIL_SAFEARRAY_) && !defined(__WIL_SAFEARRAY_SHARED_STL) && defined(WIL_RESOURCE_STL) +#define __WIL_SAFEARRAY_SHARED_STL + template + using shared_safearray_nothrow_t = shared_any>; + template + using shared_safearray_failfast_t = shared_any>; + + using shared_safearray_nothrow = shared_any; + using shared_safearray_failfast = shared_any; + using shared_char_safearray_nothrow = shared_any; + using shared_char_safearray_failfast = shared_any; + //using shared_short_safearray_nothrow = shared_any; + //using shared_short_safearray_failfast = shared_any; + using shared_long_safearray_nothrow = shared_any; + using shared_long_safearray_failfast = shared_any; + using shared_int_safearray_nothrow = shared_any; + using shared_int_safearray_failfast = shared_any; + using shared_longlong_safearray_nothrow = shared_any; + using shared_longlong_safearray_failfast = shared_any; + using shared_byte_safearray_nothrow = shared_any; + using shared_byte_safearray_failfast = shared_any; + using shared_word_safearray_nothrow = shared_any; + using shared_word_safearray_failfast = shared_any; + using shared_dword_safearray_nothrow = shared_any; + using shared_dword_safearray_failfast = shared_any; + using shared_ulonglong_safearray_nothrow = shared_any; + using shared_ulonglong_safearray_failfast = shared_any; + using shared_float_safearray_nothrow = shared_any; + using shared_float_safearray_failfast = shared_any; + //using shared_double_safearray_nothrow = shared_any; + //using shared_double_safearray_failfast = shared_any; + using shared_varbool_safearray_nothrow = shared_any; + using shared_varbool_safearray_failfast = shared_any; + using shared_date_safearray_nothrow = shared_any; + using shared_date_safearray_failfast = shared_any; + using shared_currency_safearray_nothrow = shared_any; + using shared_currency_safearray_failfast = shared_any; + using shared_decimal_safearray_nothrow = shared_any; + using shared_decimal_safearray_failfast = shared_any; + using shared_bstr_safearray_nothrow = shared_any; + using shared_bstr_safearray_failfast = shared_any; + using shared_unknown_safearray_nothrow = shared_any; + using shared_unknown_safearray_failfast = shared_any; + using shared_dispatch_safearray_nothrow = shared_any; + using shared_dispatch_safearray_failfast = shared_any; + using shared_variant_safearray_nothrow = shared_any; + using shared_variant_safearray_failfast = shared_any; + +#ifdef WIL_ENABLE_EXCEPTIONS + template + using shared_safearray_t = shared_any>; + + using shared_safearray = shared_any; + using shared_char_safearray = shared_any; + //using shared_short_safearray = shared_any; + using shared_long_safearray = shared_any; + using shared_int_safearray = shared_any; + using shared_longlong_safearray = shared_any; + using shared_byte_safearray = shared_any; + using shared_word_safearray = shared_any; + using shared_dword_safearray = shared_any; + using shared_ulonglong_safearray = shared_any; + using shared_float_safearray = shared_any; + //using shared_double_safearray = shared_any; + using shared_varbool_safearray = shared_any; + using shared_date_safearray = shared_any; + using shared_currency_safearray = shared_any; + using shared_decimal_safearray = shared_any; + using shared_bstr_safearray = shared_any; + using shared_unknown_safearray = shared_any; + using shared_dispatch_safearray = shared_any; + using shared_variant_safearray = shared_any; +#endif + + template + using weak_safearray_nothrow_t = weak_any>; + template + using weak_safearray_failfast_t = weak_any>; + + using weak_safearray_nothrow = weak_any; + using weak_safearray_failfast = weak_any; + using weak_char_safearray_nothrow = weak_any; + using weak_char_safearray_failfast = weak_any; + //using weak_short_safearray_nothrow = weak_any; + //using weak_short_safearray_failfast = weak_any; + using weak_long_safearray_nothrow = weak_any; + using weak_long_safearray_failfast = weak_any; + using weak_int_safearray_nothrow = weak_any; + using weak_int_safearray_failfast = weak_any; + using weak_longlong_safearray_nothrow = weak_any; + using weak_longlong_safearray_failfast = weak_any; + using weak_byte_safearray_nothrow = weak_any; + using weak_byte_safearray_failfast = weak_any; + using weak_word_safearray_nothrow = weak_any; + using weak_word_safearray_failfast = weak_any; + using weak_dword_safearray_nothrow = weak_any; + using weak_dword_safearray_failfast = weak_any; + using weak_ulonglong_safearray_nothrow = weak_any; + using weak_ulonglong_safearray_failfast = weak_any; + using weak_float_safearray_nothrow = weak_any; + using weak_float_safearray_failfast = weak_any; + //using weak_double_safearray_nothrow = weak_any; + //using weak_double_safearray_failfast = weak_any; + using weak_varbool_safearray_nothrow = weak_any; + using weak_varbool_safearray_failfast = weak_any; + using weak_date_safearray_nothrow = weak_any; + using weak_date_safearray_failfast = weak_any; + using weak_currency_safearray_nothrow = weak_any; + using weak_currency_safearray_failfast = weak_any; + using weak_decimal_safearray_nothrow = weak_any; + using weak_decimal_safearray_failfast = weak_any; + using weak_bstr_safearray_nothrow = weak_any; + using weak_bstr_safearray_failfast = weak_any; + using weak_unknown_safearray_nothrow = weak_any; + using weak_unknown_safearray_failfast = weak_any; + using weak_dispatch_safearray_nothrow = weak_any; + using weak_dispatch_safearray_failfast = weak_any; + using weak_variant_safearray_nothrow = weak_any; + using weak_variant_safearray_failfast = weak_any; + +#ifdef WIL_ENABLE_EXCEPTIONS + template + using weak_safearray_t = weak_any>; + + using weak_safearray = weak_any; + using weak_char_safearray = weak_any; + //using weak_short_safearray = weak_any; + using weak_long_safearray = weak_any; + using weak_int_safearray = weak_any; + using weak_longlong_safearray = weak_any; + using weak_byte_safearray = weak_any; + using weak_word_safearray = weak_any; + using weak_dword_safearray = weak_any; + using weak_ulonglong_safearray = weak_any; + using weak_float_safearray = weak_any; + //using weak_double_safearray = weak_any; + using weak_varbool_safearray = weak_any; + using weak_date_safearray = weak_any; + using weak_currency_safearray = weak_any; + using weak_decimal_safearray = weak_any; + using weak_bstr_safearray = weak_any; + using weak_unknown_safearray = weak_any; + using weak_dispatch_safearray = weak_any; + using weak_variant_safearray = weak_any; +#endif + +#endif // __WIL_SAFEARRAY_SHARED_STL + +} // namespace wil diff --git a/tests/SafearrayTests.cpp b/tests/SafearrayTests.cpp new file mode 100644 index 00000000..ec3b6ecb --- /dev/null +++ b/tests/SafearrayTests.cpp @@ -0,0 +1,1574 @@ +#include +#include +#include + +#include "common.h" +#include +#include + +#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) + +#pragma comment( lib, "Propsys.lib" ) + +#define RUN_TYPED_TEST_NOTHROW(test) WI_FOREACH(test \ + , wil::unique_char_safearray_nothrow \ + , wil::unique_long_safearray_nothrow \ + , wil::unique_int_safearray_nothrow \ + , wil::unique_longlong_safearray_nothrow \ + , wil::unique_byte_safearray_nothrow \ + , wil::unique_word_safearray_nothrow \ + , wil::unique_dword_safearray_nothrow \ + , wil::unique_ulonglong_safearray_nothrow \ + , wil::unique_float_safearray_nothrow \ + , wil::unique_varbool_safearray_nothrow \ + , wil::unique_date_safearray_nothrow \ + , wil::unique_bstr_safearray_nothrow \ + , wil::unique_unknown_safearray_nothrow \ + , wil::unique_dispatch_safearray_nothrow \ + , wil::unique_variant_safearray_nothrow \ + ) + +#define RUN_TYPED_TEST_FAILFAST(test) WI_FOREACH(test \ + , wil::unique_char_safearray_failfast \ + , wil::unique_long_safearray_failfast \ + , wil::unique_int_safearray_failfast \ + , wil::unique_longlong_safearray_failfast \ + , wil::unique_byte_safearray_failfast \ + , wil::unique_word_safearray_failfast \ + , wil::unique_dword_safearray_failfast \ + , wil::unique_ulonglong_safearray_failfast \ + , wil::unique_float_safearray_failfast \ + , wil::unique_varbool_safearray_failfast \ + , wil::unique_date_safearray_failfast \ + , wil::unique_bstr_safearray_failfast \ + , wil::unique_unknown_safearray_failfast \ + , wil::unique_dispatch_safearray_failfast \ + , wil::unique_variant_safearray_failfast \ + ) + +#define RUN_TYPED_TEST(test) WI_FOREACH(test \ + , wil::unique_char_safearray \ + , wil::unique_long_safearray \ + , wil::unique_int_safearray \ + , wil::unique_longlong_safearray \ + , wil::unique_byte_safearray \ + , wil::unique_word_safearray \ + , wil::unique_dword_safearray \ + , wil::unique_ulonglong_safearray \ + , wil::unique_float_safearray \ + , wil::unique_varbool_safearray \ + , wil::unique_date_safearray \ + , wil::unique_bstr_safearray \ + , wil::unique_unknown_safearray \ + , wil::unique_dispatch_safearray \ + , wil::unique_variant_safearray \ + ) + +#define RUN_TEST(test) WI_FOREACH(test \ + , char \ + , short \ + , long \ + , int \ + , long long \ + , unsigned char \ + , unsigned short \ + , unsigned long \ + , unsigned int \ + , unsigned long long \ + , float \ + , double \ + , VARIANT_BOOL \ + , DATE \ + , BSTR \ + , LPUNKNOWN \ + , LPDISPATCH \ + , VARIANT \ + ) + +#define RUN_TEST_SPECIAL(test) WI_FOREACH(test \ + , BSTR \ + , LPUNKNOWN \ + , LPDISPATCH \ + , VARIANT \ + ) + + +// {5D80EC64-6694-4F49-B0B9-CCAA65467D12} +//static const GUID IID_IAmForTesting = +//{ 0x5d80ec64, 0x6694, 0x4f49, { 0xb0, 0xb9, 0xcc, 0xaa, 0x65, 0x46, 0x7d, 0x12 } }; + +interface __declspec(uuid("5D80EC64-6694-4F49-B0B9-CCAA65467D12")) + IAmForTesting : public IUnknown +{ + STDMETHOD(GetID)(LONG* pOut) = 0; +}; + +class TestComObject : virtual public IAmForTesting, + virtual public IDispatch +{ +private: + TestComObject() + { + ::InterlockedIncrement(&s_ObjectCount); + m_nID = ::InterlockedIncrement(&s_IDCount); + } + + virtual ~TestComObject() + { + ::InterlockedDecrement(&s_ObjectCount); + } + + // IUnknown +public: + IFACEMETHODIMP_(ULONG) AddRef() + { + ::InterlockedIncrement(&s_GlobalRefCount); + ::InterlockedIncrement(&m_nRefCount); + return m_nRefCount; + } + IFACEMETHODIMP_(ULONG) Release() + { + ::InterlockedDecrement(&s_GlobalRefCount); + ULONG ulRefCount = ::InterlockedDecrement(&m_nRefCount); + if (0 == m_nRefCount) + { + delete this; + } + return ulRefCount; + } + STDMETHOD(QueryInterface)(REFIID riid, LPVOID* ppv) + { + if (!ppv) + return E_INVALIDARG; + *ppv = NULL; + if (riid == __uuidof(IUnknown) || riid == __uuidof(IAmForTesting)) + { + *ppv = (LPVOID)(IAmForTesting*)this; + AddRef(); + return NOERROR; + } + else if (riid == __uuidof(IDispatch)) + { + *ppv = (LPVOID)(IDispatch*)this; + AddRef(); + return NOERROR; + } + return E_NOINTERFACE; + } + + // IDispatch +public: + STDMETHOD(GetTypeInfoCount)(UINT* /*pctinfo*/) + { + return E_NOTIMPL; + } + + STDMETHOD(GetTypeInfo)(UINT /*itinfo*/, LCID /*lcid*/, ITypeInfo** /*pptinfo*/) + { + return E_NOTIMPL; + } + + STDMETHOD(GetIDsOfNames)(REFIID /*riid*/, LPOLESTR* /*rgszNames*/, UINT /*cNames*/, LCID /*lcid*/, DISPID* /*rgdispid*/) + { + return E_NOTIMPL; + } + + STDMETHOD(Invoke)(DISPID /*dispidMember*/, REFIID /*riid*/, LCID /*lcid*/, WORD /*wFlags*/, DISPPARAMS* /*pdispparams*/, VARIANT* /*pvarResult*/, EXCEPINFO* /*pexcepinfo*/, UINT* /*puArgErr*/) + { + return E_NOTIMPL; + } + + // IAmForTesting +public: + STDMETHOD(GetID)(LONG* pOut) + { + if (!pOut) return E_POINTER; + *pOut = m_nID; + return S_OK; + } + + // Factory Method +public: + static HRESULT Create(REFIID riid, LPVOID* ppvObj) + { + auto* p = new (std::nothrow) TestComObject(); + if (!p) return E_OUTOFMEMORY; + auto hr = p->QueryInterface(riid, ppvObj); + if (FAILED(hr)) + { + delete p; + } + return hr; + } + + static bool Compare(LPUNKNOWN left, LPUNKNOWN right) + { + if (left == nullptr && right == nullptr) + { + return true; + } + if (left == nullptr || right == nullptr) + { + return false; + } + if (left == right) + { + return true; + } + + wil::com_ptr_nothrow spLeft; + wil::com_ptr_nothrow spRight; + + if (SUCCEEDED(left->QueryInterface(&spLeft)) && SUCCEEDED(right->QueryInterface(&spRight))) + { + LONG nLeft = 0; + LONG nRight = 0; + + REQUIRE_SUCCEEDED(spLeft->GetID(&nLeft)); + REQUIRE_SUCCEEDED(spRight->GetID(&nRight)); + + return (nLeft == nRight); + } + else + { + return false; + } + } + + static LONG ObjectCount() { return s_ObjectCount; } + static LONG GlobalRefCount() { return s_GlobalRefCount; } + +private: + LONG m_nRefCount{}; + LONG m_nID{}; + + static LONG s_ObjectCount; + static LONG s_GlobalRefCount; + static LONG s_IDCount; +}; + +LONG TestComObject::s_ObjectCount = 0; +LONG TestComObject::s_GlobalRefCount = 0; +LONG TestComObject::s_IDCount = 0; + +constexpr auto DEFAULT_SAMPLE_SIZE = 192U; + +template::value, int>::type> +std::array GetSampleData() +{ + constexpr auto BIT_COUNT = sizeof(T) * 8; + + auto result = std::array{}; + for (auto i = 0U; i < BIT_COUNT; ++i) + { + result[i] = (static_cast(1) << i); + } + return result; +} + +template::value, int>::type> +std::array GetSampleData() +{ + auto result = std::array{}; + for (auto i = 0U; i < DEFAULT_SAMPLE_SIZE/2; ++i) + { + result[i] = (i != 0) ? static_cast(1 / i) : 0; + result[i + (DEFAULT_SAMPLE_SIZE/2)] = 2 * static_cast(i); + } + return result; +} + +template::value, int>::type> +std::array GetSampleData() +{ + auto result = std::array{}; + for (auto i = 0U; i < DEFAULT_SAMPLE_SIZE; ++i) + { + switch (i % 4) + { + case 0: + result[i].reset(::SysAllocString(L"Sample Data")); + break; + case 1: + result[i].reset(::SysAllocString(L"Larger Sample Data")); + break; + case 2: + result[i].reset(::SysAllocString(L"This is much much larger Sample Data")); + break; + case 3: + result[i].reset(::SysAllocString(L"This is the longest Sample Data. It's the longest by a lot. I mean a lot.")); + break; + } + } + return result; +} + +template::value, int>::type> +std::array GetSampleData() +{ + auto result = std::array{}; + for (auto i = 0U; i < DEFAULT_SAMPLE_SIZE; ++i) + { + auto& var = result[i]; + switch (i % 6) + { + case 0: + V_VT(&var) = VT_I4; + V_I4(&var) = 37; + break; + case 1: + V_VT(&var) = VT_I1; + V_I1(&var) = 0x40; + break; + case 2: + V_VT(&var) = VT_BSTR; + V_BSTR(&var) = ::SysAllocString(L"String in a variant"); + break; + case 3: + V_VT(&var) = VT_R4; + V_R4(&var) = 98.6f; + break; + case 4: + V_VT(&var) = VT_UNKNOWN; + REQUIRE_SUCCEEDED(TestComObject::Create(IID_IUnknown, (LPVOID*)&(V_UNKNOWN(&var)))); + break; + case 5: + V_VT(&var) = VT_DISPATCH; + REQUIRE_SUCCEEDED(TestComObject::Create(IID_IDispatch, (LPVOID*)&(V_DISPATCH(&var)))); + break; + } + } + return result; +} + +template::value, int>::type> +std::array, DEFAULT_SAMPLE_SIZE> GetSampleData() +{ + auto result = std::array, DEFAULT_SAMPLE_SIZE>{}; + for (auto i = 0U; i < DEFAULT_SAMPLE_SIZE; ++i) + { + auto& var = result[i]; + REQUIRE_SUCCEEDED(TestComObject::Create(__uuidof(IAmForTesting), (LPVOID*)var.put())); + } + return result; +} + +template::value, int>::type> +std::array, DEFAULT_SAMPLE_SIZE> GetSampleData() +{ + auto result = std::array, DEFAULT_SAMPLE_SIZE>{}; + for (auto i = 0U; i < DEFAULT_SAMPLE_SIZE; ++i) + { + auto& var = result[i]; + REQUIRE_SUCCEEDED(TestComObject::Create(__uuidof(IDispatch), (LPVOID*)var.put())); + } + return result; +} + +template ::value + && !wistd::is_same>::value + && !wistd::is_same>::value, int>::type> +auto GetReadable(const T& t) -> const T& +{ + return t; +} + +template::value, int>::type> +auto GetReadable(const T& t) -> BSTR +{ + return t.get(); +} + +template>::value, int>::type> +auto GetReadable(const T& t) -> LPUNKNOWN +{ + return t.get(); +} + +template>::value, int>::type> +auto GetReadable(const T& t) -> LPDISPATCH +{ + return t.get(); +} + +template ::value + && !wistd::is_same>::value + && !wistd::is_same>::value, int>::type> +auto GetWritable(T& t) -> T& +{ + return t; +} + +template::value, int>::type> +auto GetWritable(T& t) -> BSTR& +{ + return *(t.addressof()); +} + +template>::value, int>::type> +auto GetWritable(T& t) -> LPUNKNOWN& +{ + return *((LPUNKNOWN*)t.addressof()); +} + +template>::value, int>::type> +auto GetWritable(T& t) -> LPDISPATCH& +{ + return *(t.addressof()); +} + +template +bool PerformCompare(const T& left, const U& right) +{ + return (left == right); +} + +template<> +bool PerformCompare(_In_z_ const BSTR& left, _In_z_ const BSTR& right) +{ + return (::SysStringLen(left) == ::SysStringLen(right)) + && (wcscmp(left, right) == 0); +} + +template<> +bool PerformCompare(const BSTR& left, const wil::unique_bstr& right) +{ + return PerformCompare(left, right.get()); +} + +template<> +bool PerformCompare(const wil::unique_bstr& left, const BSTR& right) +{ + return PerformCompare(left.get(), right); +} + +template<> +bool PerformCompare(const wil::unique_bstr& left, const wil::unique_bstr& right) +{ + return PerformCompare(left.get(), right.get()); +} + +template<> +bool PerformCompare(const wil::unique_variant& left, const wil::unique_variant& right) +{ + return (::VariantCompare(left, right) == 0); +} + +template<> +bool PerformCompare(const VARIANT& left, const wil::unique_variant& right) +{ + return (::VariantCompare(left, right) == 0); +} + +template<> +bool PerformCompare(const VARIANT& left, const VARIANT& right) +{ + return (::VariantCompare(left, right) == 0); +} + +template<> +bool PerformCompare(const LPUNKNOWN& left, const LPUNKNOWN& right) +{ + return TestComObject::Compare(left, right); +} + +template<> +bool PerformCompare(const wil::com_ptr_nothrow& left, const LPUNKNOWN& right) +{ + return TestComObject::Compare(left.get(), right); +} + +template<> +bool PerformCompare(const LPUNKNOWN& left, const wil::com_ptr_nothrow& right) +{ + return TestComObject::Compare(left, right.get()); +} + +template +void PerfromAssignment(T& t, const U& u) +{ + t = u; +} + +template<> +void PerfromAssignment(VARIANT& t, const wil::unique_variant& u) +{ + FAIL_FAST_IF_FAILED(::VariantCopy(&t, &u)); +} + +template<> +void PerfromAssignment(BSTR& t, const wil::unique_bstr& u) +{ + t = ::SysAllocString(u.get()); +} + +template<> +void PerfromAssignment(LPUNKNOWN& t, const wil::com_ptr_nothrow& u) +{ + if (t) + { + t->Release(); + t = nullptr; + } + if (u) + { + REQUIRE_SUCCEEDED(u->QueryInterface(&t)); + } +} + +template +void PerfromAssignment(I*& t, const wil::com_ptr_nothrow& u) +{ + if (t) + { + t->Release(); + t = nullptr; + } + if (u) + { + REQUIRE_SUCCEEDED(u->QueryInterface(IID_PPV_ARGS(&t))); + } +} + +template +void TestLock(safearray_t& sa) +{ + REQUIRE(sa); + const auto startingLocks = sa.lock_count(); + { + auto lock = sa.scope_lock(); + REQUIRE(sa.lock_count() > startingLocks); // Verify Lock Count increased + } + REQUIRE(startingLocks == sa.lock_count()); // Verify it dropped back down +} + +template +void TestTyped_Create_NoThrow() +{ + constexpr auto SIZE = DEFAULT_SAMPLE_SIZE; + + auto sa = safearray_t{}; + LONG val = 0; + ULONG size = 0; + REQUIRE_SUCCEEDED(sa.create(SIZE)); + REQUIRE(sa); + REQUIRE(sa.dims() == 1); + REQUIRE(sa.elemsize() == sizeof(typename safearray_t::elemtype)); + REQUIRE_SUCCEEDED(sa.size(&size)); + REQUIRE(size == SIZE); + REQUIRE_SUCCEEDED(sa.lbound(&val)); + REQUIRE(val == 0); + REQUIRE_SUCCEEDED(sa.ubound(&val)); + REQUIRE(val == SIZE - 1); + TestLock(sa); + sa.reset(); + REQUIRE(!sa); +} + +template +void TestTyped_Create_FailFast() +{ + constexpr auto SIZE = DEFAULT_SAMPLE_SIZE; + + auto sa = safearray_t{}; + LONG val = 0; + ULONG size = 0; + REQUIRE_NOCRASH(sa.create(SIZE)); + REQUIRE(sa); + REQUIRE(sa.dims() == 1); + REQUIRE(sa.elemsize() == sizeof(typename safearray_t::elemtype)); + REQUIRE_NOCRASH(sa.size(&size)); + REQUIRE(size == SIZE); + REQUIRE_NOCRASH(sa.lbound(&val)); + REQUIRE(val == 0); + REQUIRE_NOCRASH(sa.ubound(&val)); + REQUIRE(val == SIZE - 1); + TestLock(sa); + sa.reset(); + REQUIRE(!sa); +} + +#ifdef WIL_ENABLE_EXCEPTIONS +template +void TestTyped_Create() +{ + constexpr auto SIZE = DEFAULT_SAMPLE_SIZE; + + auto sa = safearray_t{}; + REQUIRE_NOTHROW(sa = safearray_t{ SIZE }); + REQUIRE(sa); + REQUIRE(sa.dims() == 1); + REQUIRE(sa.elemsize() == sizeof(typename safearray_t::elemtype)); + REQUIRE_NOTHROW(sa.size() == SIZE); + REQUIRE_NOTHROW(sa.lbound() == 0); + REQUIRE_NOTHROW(sa.ubound() == SIZE-1); + TestLock(sa); + sa.reset(); + REQUIRE(!sa); +} +#endif + +#define _TYPED_CREATE_NOTHROW(type) TestTyped_Create_NoThrow(); +#define _TYPED_CREATE_FAILFAST(type) TestTyped_Create_FailFast(); +#define _TYPED_CREATE(type) TestTyped_Create(); + +template +void Test_Create_NoThrow() +{ + constexpr auto SIZE = DEFAULT_SAMPLE_SIZE; + + auto sa = wil::unique_safearray_nothrow{}; + LONG val = 0; + ULONG size = 0; + REQUIRE_SUCCEEDED(sa.create(SIZE)); + REQUIRE(sa); + REQUIRE(sa.dims() == 1); + REQUIRE(sa.elemsize() == sizeof(T)); + REQUIRE_SUCCEEDED(sa.size(&size)); + REQUIRE(size == SIZE); + REQUIRE_SUCCEEDED(sa.lbound(&val)); + REQUIRE(val == 0); + REQUIRE_SUCCEEDED(sa.ubound(&val)); + REQUIRE(val == SIZE - 1); + TestLock(sa); + sa.reset(); + REQUIRE(!sa); +} + +template +void Test_Create_FailFast() +{ + constexpr auto SIZE = DEFAULT_SAMPLE_SIZE; + + auto sa = wil::unique_safearray_failfast{}; + LONG val = 0; + ULONG size = 0; + REQUIRE_NOCRASH(sa.create(SIZE)); + REQUIRE(sa); + REQUIRE(sa.dims() == 1); + REQUIRE(sa.elemsize() == sizeof(T)); + REQUIRE_NOCRASH(sa.size(&size)); + REQUIRE(size == SIZE); + REQUIRE_NOCRASH(sa.lbound(&val)); + REQUIRE(val == 0); + REQUIRE_NOCRASH(sa.ubound(&val)); + REQUIRE(val == SIZE - 1); + TestLock(sa); + sa.reset(); + REQUIRE(!sa); +} + +#ifdef WIL_ENABLE_EXCEPTIONS +template +void Test_Create() +{ + constexpr auto SIZE = DEFAULT_SAMPLE_SIZE; + + auto sa = wil::unique_safearray{}; + REQUIRE_NOTHROW(sa.create( SIZE )); + REQUIRE(sa); + REQUIRE(sa.dims() == 1); + REQUIRE(sa.elemsize() == sizeof(T)); + REQUIRE_NOTHROW(sa.size() == SIZE); + REQUIRE_NOTHROW(sa.lbound() == 0); + REQUIRE_NOTHROW(sa.ubound() == SIZE - 1); + TestLock(sa); + sa.reset(); + REQUIRE(!sa); +} +#endif + +#define _CREATE_NOTHROW(type) Test_Create_NoThrow(); +#define _CREATE_FAILFAST(type) Test_Create_FailFast(); +#define _CREATE(type) Test_Create(); + +template +void TestTyped_Element_NoThrow() +{ + auto sample_data = GetSampleData(); + auto SIZE = ULONG{ sample_data.size() }; + + using array_type = decltype(sample_data); + using data_type = typename array_type::value_type; + + auto sa = safearray_t{}; + + REQUIRE_SUCCEEDED(sa.create(SIZE)); + REQUIRE(sa); + + // Loop through and set the values with put_element + for (ULONG i = 0; i < SIZE; ++i) + { + REQUIRE_SUCCEEDED(sa.put_element(i, GetReadable(sample_data[i]))); + } + + // Loop through and get the values with get_element + for (ULONG i = 0; i < SIZE; ++i) + { + { + auto temp = data_type{}; + // And make sure it was the value that was set + REQUIRE_SUCCEEDED(sa.get_element(i, GetWritable(temp))); + REQUIRE(PerformCompare(temp, sample_data[i])); + } + } +} + +template +void TestTyped_Element_Failfast() +{ + auto sample_data = GetSampleData(); + auto SIZE = ULONG{ sample_data.size() }; + + using array_type = decltype(sample_data); + using data_type = typename array_type::value_type; + + auto sa = safearray_t{}; + + REQUIRE_NOCRASH(sa.create(SIZE)); + REQUIRE(sa); + + // Loop through and set the values with put_element + for (ULONG i = 0; i < SIZE; ++i) + { + REQUIRE_NOCRASH(sa.put_element(i, GetReadable(sample_data[i]))); + } + + // Loop through and get the values with get_element + for (ULONG i = 0; i < SIZE; ++i) + { + { + auto temp = data_type{}; + // And make sure it was the value that was set + REQUIRE_NOCRASH(sa.get_element(i, GetWritable(temp))); + REQUIRE(PerformCompare(temp, sample_data[i])); + } + } +} + +#ifdef WIL_ENABLE_EXCEPTIONS +template +void TestTyped_Element() +{ + auto sample_data = GetSampleData(); + auto SIZE = ULONG{ sample_data.size() }; + + using array_type = decltype(sample_data); + using data_type = typename array_type::value_type; + + auto sa = safearray_t{}; + + REQUIRE_NOTHROW(sa.create(SIZE)); + REQUIRE(sa); + + // Loop through and set the values with put_element + for (ULONG i = 0; i < SIZE; ++i) + { + REQUIRE_NOTHROW(sa.put_element(i, GetReadable(sample_data[i]))); + } + + // Loop through and get the values with get_element + for (ULONG i = 0; i < SIZE; ++i) + { + { + auto temp = data_type{}; + // And make sure it was the value that was set + REQUIRE_NOTHROW(sa.get_element(i, GetWritable(temp))); + REQUIRE(PerformCompare(temp, sample_data[i])); + } + } +} +#endif + +#define _TYPED_DIRECT_NOTHROW(type) TestTyped_Element_NoThrow(); +#define _TYPED_DIRECT_FAILFAST(type) TestTyped_Element_Failfast(); +#define _TYPED_DIRECT(type) TestTyped_Element(); + +template +void Test_Element_NoThrow() +{ + auto sample_data = GetSampleData(); + auto SIZE = ULONG{ sample_data.size() }; + + using array_type = decltype(sample_data); + using data_type = typename array_type::value_type; + + auto sa = wil::unique_safearray_nothrow{}; + + REQUIRE_SUCCEEDED(sa.create(SIZE)); + REQUIRE(sa); + + // Loop through and set the values with put_element + for (ULONG i = 0; i < SIZE; ++i) + { + REQUIRE_SUCCEEDED(sa.put_element(i, GetReadable(sample_data[i]))); + } + + // Loop through and get the values with get_element + for (ULONG i = 0; i < SIZE; ++i) + { + { + auto temp = data_type{}; + // And make sure it was the value that was set + REQUIRE_SUCCEEDED(sa.get_element(i, GetWritable(temp))); + REQUIRE(PerformCompare(temp, sample_data[i])); + } + } +} + +template +void Test_Element_Failfast() +{ + auto sample_data = GetSampleData(); + auto SIZE = ULONG{ sample_data.size() }; + + using array_type = decltype(sample_data); + using data_type = typename array_type::value_type; + + auto sa = wil::unique_safearray_failfast{}; + + REQUIRE_NOCRASH(sa.create(SIZE)); + REQUIRE(sa); + + // Loop through and set the values with put_element + for (ULONG i = 0; i < SIZE; ++i) + { + REQUIRE_NOCRASH(sa.put_element(i, GetReadable(sample_data[i]))); + } + + // Loop through and get the values with get_element + for (ULONG i = 0; i < SIZE; ++i) + { + { + auto temp = data_type{}; + // And make sure it was the value that was set + REQUIRE_NOCRASH(sa.get_element(i, GetWritable(temp))); + REQUIRE(PerformCompare(temp, sample_data[i])); + } + } +} + +#ifdef WIL_ENABLE_EXCEPTIONS +template +void Test_Element() +{ + auto sample_data = GetSampleData(); + auto SIZE = ULONG{ sample_data.size() }; + + using array_type = decltype(sample_data); + using data_type = typename array_type::value_type; + + auto sa = wil::unique_safearray{}; + + REQUIRE_NOTHROW(sa.create(SIZE)); + REQUIRE(sa); + + // Loop through and set the values with put_element + for (ULONG i = 0; i < SIZE; ++i) + { + REQUIRE_NOTHROW(sa.put_element(i, GetReadable(sample_data[i]))); + } + + // Loop through and get the values with get_element + for (ULONG i = 0; i < SIZE; ++i) + { + { + auto temp = data_type{}; + // And make sure it was the value that was set + REQUIRE_NOTHROW(sa.get_element(i, GetWritable(temp))); + REQUIRE(PerformCompare(temp, sample_data[i])); + } + } +} +#endif + +#define _DIRECT_NOTHROW(type) Test_Element_NoThrow(); +#define _DIRECT_FAILFAST(type) Test_Element_Failfast(); +#define _DIRECT(type) Test_Element(); + +template +void TestTyped_AccessData_NoThrow() +{ + auto sample_data = GetSampleData(); + auto SIZE = ULONG{ sample_data.size() }; + + // Operate on the AccessData class using ranged-for + { + // Create a new SA and copy the sample data into it + auto sa = safearray_t{}; + REQUIRE_SUCCEEDED(sa.create(SIZE)); + REQUIRE(sa); + { + ULONG counter = {}; + wil::safearraydata_nothrow data; + REQUIRE_SUCCEEDED(data.access(sa.get())); + for (auto& elem : data) + { + PerfromAssignment(elem, sample_data[counter++]); + } + } + + // Duplicate the SA to make sure create_copy works + auto sa2 = safearray_t{}; + REQUIRE_SUCCEEDED(sa2.create_copy(sa.get())); + REQUIRE(sa2); + { + // Verify the values in the copy are the same as + // the values that were placed into the original + ULONG counter = {}; + wil::safearraydata_nothrow data; + REQUIRE_SUCCEEDED(data.access(sa2.get())); + for (const auto& elem : data) + { + REQUIRE(PerformCompare(elem, sample_data[counter++])); + } + } + } + + // Operate on the AccessData class using regular for-loop + { + // Create a new SA and copy the sample data into it + auto sa = safearray_t{}; + REQUIRE_SUCCEEDED(sa.create(SIZE)); + REQUIRE(sa); + { + wil::safearraydata_nothrow data; + REQUIRE_SUCCEEDED(data.access(sa.get())); + for (ULONG i = 0 ; i < data.size(); ++i) + { + PerfromAssignment(data[i], sample_data[i]); + } + } + + // Duplicate the SA to make sure create_copy works + auto sa2 = safearray_t{}; + REQUIRE_SUCCEEDED(sa2.create_copy(sa.get())); + REQUIRE(sa2); + { + // Verify the values in the copy are the same as + // the values that were placed into the original + wil::safearraydata_nothrow data; + REQUIRE_SUCCEEDED(data.access(sa2.get())); + for (ULONG i = 0; i < data.size(); ++i) + { + REQUIRE(PerformCompare(data[i], sample_data[i])); + } + } + } +} + +template +void TestTyped_AccessData_Failfast() +{ + auto sample_data = GetSampleData(); + auto SIZE = ULONG{ sample_data.size() }; + + // Operate on the AccessData class using ranged-for + { + // Create a new SA and copy the sample data into it + auto sa = safearray_t{}; + REQUIRE_NOCRASH(sa.create(SIZE)); + REQUIRE(sa); + { + ULONG counter = {}; + wil::safearraydata_failfast data; + REQUIRE_NOCRASH(data.access(sa.get())); + for (auto& elem : data) + { + PerfromAssignment(elem, sample_data[counter++]); + } + } + + // Duplicate the SA to make sure create_copy works + auto sa2 = safearray_t{}; + REQUIRE_NOCRASH(sa2.create_copy(sa.get())); + REQUIRE(sa2); + { + // Verify the values in the copy are the same as + // the values that were placed into the original + ULONG counter = {}; + wil::safearraydata_failfast data; + REQUIRE_NOCRASH(data.access(sa2.get())); + for (const auto& elem : data) + { + REQUIRE(PerformCompare(elem, sample_data[counter++])); + } + } + } + + // Operate on the AccessData class using regular for-loop + { + // Create a new SA and copy the sample data into it + auto sa = safearray_t{}; + REQUIRE_NOCRASH(sa.create(SIZE)); + REQUIRE(sa); + { + wil::safearraydata_failfast data; + REQUIRE_NOCRASH(data.access(sa.get())); + for (ULONG i = 0; i < data.size(); ++i) + { + PerfromAssignment(data[i], sample_data[i]); + } + } + + // Duplicate the SA to make sure create_copy works + auto sa2 = safearray_t{}; + REQUIRE_NOCRASH(sa2.create_copy(sa.get())); + REQUIRE(sa2); + { + // Verify the values in the copy are the same as + // the values that were placed into the original + wil::safearraydata_failfast data; + REQUIRE_NOCRASH(data.access(sa2.get())); + for (ULONG i = 0; i < data.size(); ++i) + { + REQUIRE(PerformCompare(data[i], sample_data[i])); + } + } + } +} + +#ifdef WIL_ENABLE_EXCEPTIONS +template +void TestTyped_AccessData() +{ + auto sample_data = GetSampleData(); + auto SIZE = ULONG{ sample_data.size() }; + + // Operate on the AccessData class using ranged-for + { + // Create a new SA and copy the sample data into it + auto sa = safearray_t{}; + REQUIRE_NOTHROW(sa.create(SIZE)); + REQUIRE(sa); + + REQUIRE_NOTHROW([&]() + { + ULONG counter = {}; + for (auto& elem : sa.access_data()) + { + PerfromAssignment(elem, sample_data[counter++]); + } + }()); + + // Duplicate the SA to make sure copy works + auto sa2 = safearray_t{}; + REQUIRE_NOTHROW([&]() + { + sa2 = sa.create_copy(); + REQUIRE(sa2); + }()); + + REQUIRE_NOTHROW([&]() + { + // Verify the values in the copy are the same as + // the values that were placed into the original + ULONG counter = {}; + for (const auto& elem : sa.access_data()) + { + REQUIRE(PerformCompare(elem, sample_data[counter++])); + } + }()); + } + + // Operate on the AccessData class using regular for-loop + { + // Create a new SA and copy the sample data into it + auto sa = safearray_t{}; + REQUIRE_NOTHROW(sa.create(SIZE)); + REQUIRE(sa); + + REQUIRE_NOTHROW([&]() + { + auto data = sa.access_data(); + for (ULONG i = 0; i < data.size(); ++i) + { + PerfromAssignment(data[i], sample_data[i]); + } + }()); + + // Duplicate the SA to make sure copy works + auto sa2 = safearray_t{}; + REQUIRE_NOTHROW([&]() + { + sa2 = sa.create_copy(); + REQUIRE(sa2); + }()); + + REQUIRE_NOTHROW([&]() + { + // Verify the values in the copy are the same as + // the values that were placed into the original + auto data = sa2.access_data(); + for (ULONG i = 0; i < data.size(); ++i) + { + REQUIRE(PerformCompare(data[i], sample_data[i])); + } + }()); + } +} +#endif + +#define _TYPED_ACCESSDATA_NOTHROW(type) TestTyped_AccessData_NoThrow(); +#define _TYPED_ACCESSDATA_FAILFAST(type) TestTyped_AccessData_Failfast(); +#define _TYPED_ACCESSDATA(type) TestTyped_AccessData(); + +template +void Test_AccessData_NoThrow() +{ + auto sample_data = GetSampleData(); + auto SIZE = ULONG{ sample_data.size() }; + + // Operate on the AccessData class using ranged-for + { + // Create a new SA and copy the sample data into it + auto sa = wil::unique_safearray_nothrow{}; + REQUIRE_SUCCEEDED(sa.create(SIZE)); + REQUIRE(sa); + { + ULONG counter = {}; + wil::safearraydata_nothrow data; + REQUIRE_SUCCEEDED(data.access(sa.get())); + for (auto& elem : data) + { + PerfromAssignment(elem, sample_data[counter++]); + } + } + + // Duplicate the SA to make sure create_copy works + auto sa2 = wil::unique_safearray_nothrow{}; + REQUIRE_SUCCEEDED(sa2.create_copy(sa.get())); + REQUIRE(sa2); + { + // Verify the values in the copy are the same as + // the values that were placed into the original + ULONG counter = {}; + wil::safearraydata_nothrow data; + REQUIRE_SUCCEEDED(data.access(sa2.get())); + for (const auto& elem : data) + { + REQUIRE(PerformCompare(elem, sample_data[counter++])); + } + } + } + + // Operate on the AccessData class using regular for-loop + { + // Create a new SA and copy the sample data into it + auto sa = wil::unique_safearray_nothrow{}; + REQUIRE_SUCCEEDED(sa.create(SIZE)); + REQUIRE(sa); + { + wil::safearraydata_nothrow data; + REQUIRE_SUCCEEDED(data.access(sa.get())); + for (ULONG i = 0; i < data.size(); ++i) + { + PerfromAssignment(data[i], sample_data[i]); + } + } + + // Duplicate the SA to make sure create_copy works + auto sa2 = wil::unique_safearray_nothrow{}; + REQUIRE_SUCCEEDED(sa2.create_copy(sa.get())); + REQUIRE(sa2); + { + // Verify the values in the copy are the same as + // the values that were placed into the original + wil::safearraydata_nothrow data; + REQUIRE_SUCCEEDED(data.access(sa2.get())); + for (ULONG i = 0; i < data.size(); ++i) + { + REQUIRE(PerformCompare(data[i], sample_data[i])); + } + } + } +} + +template +void Test_AccessData_Failfast() +{ + auto sample_data = GetSampleData(); + auto SIZE = ULONG{ sample_data.size() }; + + // Operate on the AccessData class using ranged-for + { + // Create a new SA and copy the sample data into it + auto sa = wil::unique_safearray_failfast{}; + REQUIRE_NOCRASH(sa.create(SIZE)); + REQUIRE(sa); + { + ULONG counter = {}; + wil::safearraydata_failfast data; + REQUIRE_NOCRASH(data.access(sa.get())); + for (auto& elem : data) + { + PerfromAssignment(elem, sample_data[counter++]); + } + } + + // Duplicate the SA to make sure create_copy works + auto sa2 = wil::unique_safearray_failfast{}; + REQUIRE_NOCRASH(sa2.create_copy(sa.get())); + REQUIRE(sa2); + { + // Verify the values in the copy are the same as + // the values that were placed into the original + ULONG counter = {}; + wil::safearraydata_failfast data; + REQUIRE_NOCRASH(data.access(sa2.get())); + for (const auto& elem : data) + { + REQUIRE(PerformCompare(elem, sample_data[counter++])); + } + } + } + + // Operate on the AccessData class using regular for-loop + { + // Create a new SA and copy the sample data into it + auto sa = wil::unique_safearray_failfast{}; + REQUIRE_NOCRASH(sa.create(SIZE)); + REQUIRE(sa); + { + wil::safearraydata_failfast data; + REQUIRE_NOCRASH(data.access(sa.get())); + for (ULONG i = 0; i < data.size(); ++i) + { + PerfromAssignment(data[i], sample_data[i]); + } + } + + // Duplicate the SA to make sure create_copy works + auto sa2 = wil::unique_safearray_failfast{}; + REQUIRE_NOCRASH(sa2.create_copy(sa.get())); + REQUIRE(sa2); + { + // Verify the values in the copy are the same as + // the values that were placed into the original + wil::safearraydata_failfast data; + REQUIRE_NOCRASH(data.access(sa2.get())); + for (ULONG i = 0; i < data.size(); ++i) + { + REQUIRE(PerformCompare(data[i], sample_data[i])); + } + } + } +} + +#ifdef WIL_ENABLE_EXCEPTIONS +template +void Test_AccessData() +{ + auto sample_data = GetSampleData(); + auto SIZE = ULONG{ sample_data.size() }; + + // Operate on the AccessData class using ranged-for + { + // Create a new SA and copy the sample data into it + auto sa = wil::unique_safearray{}; + REQUIRE_NOTHROW(sa.create(SIZE)); + REQUIRE(sa); + + REQUIRE_NOTHROW([&]() + { + ULONG counter = {}; + for (auto& elem : sa.access_data()) + { + PerfromAssignment(elem, sample_data[counter++]); + } + }()); + + // Duplicate the SA to make sure copy works + auto sa2 = wil::unique_safearray{}; + REQUIRE_NOTHROW([&]() + { + sa2 = sa.create_copy(); + REQUIRE(sa2); + }()); + + REQUIRE_NOTHROW([&]() + { + // Verify the values in the copy are the same as + // the values that were placed into the original + ULONG counter = {}; + for (const auto& elem : sa.access_data()) + { + REQUIRE(PerformCompare(elem, sample_data[counter++])); + } + }()); + } + + // Operate on the AccessData class using regular for-loop + { + // Create a new SA and copy the sample data into it + auto sa = wil::unique_safearray{}; + REQUIRE_NOTHROW(sa.create(SIZE)); + REQUIRE(sa); + + REQUIRE_NOTHROW([&]() + { + auto data = sa.access_data(); + for (ULONG i = 0; i < data.size(); ++i) + { + PerfromAssignment(data[i], sample_data[i]); + } + }()); + + // Duplicate the SA to make sure copy works + auto sa2 = wil::unique_safearray{}; + REQUIRE_NOTHROW([&]() + { + sa2 = sa.create_copy(); + REQUIRE(sa2); + }()); + + REQUIRE_NOTHROW([&]() + { + // Verify the values in the copy are the same as + // the values that were placed into the original + auto data = sa2.access_data(); + for (ULONG i = 0; i < data.size(); ++i) + { + REQUIRE(PerformCompare(data[i], sample_data[i])); + } + }()); + } +} +#endif + +#define _ACCESSDATA_NOTHROW(type) Test_AccessData_NoThrow(); +#define _ACCESSDATA_FAILFAST(type) Test_AccessData_Failfast(); +#define _ACCESSDATA(type) Test_AccessData(); + +template +void TestTyped_Element_Special_NoThrow() +{ + auto sample_data = GetSampleData(); + auto SIZE = ULONG{ sample_data.size() }; + + using array_type = decltype(sample_data); + using data_type = typename array_type::value_type; + + auto sa = wil::unique_safearray_nothrow_t{}; + + REQUIRE_SUCCEEDED(sa.create(SIZE)); + REQUIRE(sa); + + // Loop through and set the values with put_element + for (ULONG i = 0; i < SIZE; ++i) + { + // No Need to call GetReadable + REQUIRE_SUCCEEDED(sa.put_element(i, sample_data[i])); + } + + // Can use a single instance because the wrapper classes clean up + auto temp = data_type{}; + + // Loop through and get the values with get_element + for (ULONG i = 0; i < SIZE; ++i) + { + // And make sure it was the value that was set + // No Need to Call GetWritable + REQUIRE_SUCCEEDED(sa.get_element(i, temp)); + REQUIRE(PerformCompare(temp, sample_data[i])); + } +} + +template +void TestTyped_Element_Special_Failfast() +{ + auto sample_data = GetSampleData(); + auto SIZE = ULONG{ sample_data.size() }; + + using array_type = decltype(sample_data); + using data_type = typename array_type::value_type; + + auto sa = wil::unique_safearray_failfast_t{}; + + REQUIRE_NOCRASH(sa.create(SIZE)); + REQUIRE(sa); + + // Loop through and set the values with put_element + for (ULONG i = 0; i < SIZE; ++i) + { + // No Need to call GetReadable + REQUIRE_NOCRASH(sa.put_element(i, sample_data[i])); + } + + // Can use a single instance because the wrapper classes clean up + auto temp = data_type{}; + + // Loop through and get the values with get_element + for (ULONG i = 0; i < SIZE; ++i) + { + // And make sure it was the value that was set + // No Need to Call GetWritable + REQUIRE_NOCRASH(sa.get_element(i, temp)); + REQUIRE(PerformCompare(temp, sample_data[i])); + } +} + +#ifdef WIL_ENABLE_EXCEPTIONS +template +void TestTyped_Element_Special() +{ + auto sample_data = GetSampleData(); + auto SIZE = ULONG{ sample_data.size() }; + + using array_type = decltype(sample_data); + using data_type = typename array_type::value_type; + + auto sa = wil::unique_safearray_t{}; + + REQUIRE_NOTHROW(sa.create(SIZE)); + REQUIRE(sa); + + // Loop through and set the values with put_element + for (ULONG i = 0; i < SIZE; ++i) + { + // No Need to call GetReadable + REQUIRE_NOTHROW(sa.put_element(i, sample_data[i])); + } + + // Can use a single instance because the wrapper classes clean up + auto temp = data_type{}; + + // Loop through and get the values with get_element + for (ULONG i = 0; i < SIZE; ++i) + { + // And make sure it was the value that was set + // No Need to Call GetWritable + REQUIRE_NOTHROW(sa.get_element(i, temp)); + REQUIRE(PerformCompare(temp, sample_data[i])); + } +} + +template +void TestTyped_Element_Special_Get() +{ + auto sample_data = GetSampleData(); + auto SIZE = ULONG{ sample_data.size() }; + + auto sa = wil::unique_safearray_t{}; + + REQUIRE_NOTHROW(sa.create(SIZE)); + REQUIRE(sa); + + // Loop through and set the values with put_element + for (ULONG i = 0; i < SIZE; ++i) + { + // No Need to call GetReadable + REQUIRE_NOTHROW(sa.put_element(i, sample_data[i])); + } + + // Loop through and get the values with get_element + for (ULONG i = 0; i < SIZE; ++i) + { + // And make sure it was the value that was set via the special Get function + REQUIRE(PerformCompare(sa.get_element(i), sample_data[i])); + } +} +#endif + +#define _TYPED_DIRECT_SPECIAL_NOTHROW(type) TestTyped_Element_Special_NoThrow(); +#define _TYPED_DIRECT_SPECIAL_FAILFAST(type) TestTyped_Element_Special_Failfast(); +#define _TYPED_DIRECT_SPECIAL(type) TestTyped_Element_Special(); +#define _TYPED_DIRECT_SPECIAL_GET(type) TestTyped_Element_Special_Get(); + +TEST_CASE("Safearray::Create", "[safearray]") +{ + SECTION("Create SafeArray - No Throw") + { + RUN_TYPED_TEST_NOTHROW(_TYPED_CREATE_NOTHROW); + RUN_TEST(_CREATE_NOTHROW); + } + + SECTION("Create SafeArray - FailFast") + { + RUN_TYPED_TEST_FAILFAST(_TYPED_CREATE_FAILFAST); + RUN_TEST(_CREATE_FAILFAST); + } + +#ifdef WIL_ENABLE_EXCEPTIONS + SECTION("Create SafeArray - Exceptions") + { + RUN_TYPED_TEST(_TYPED_CREATE); + RUN_TEST(_CREATE); + } +#endif +} + +TEST_CASE("Safearray::Put/Get", "[safearray]") +{ + SECTION("Direct Element Access - No Throw") + { + RUN_TYPED_TEST_NOTHROW(_TYPED_DIRECT_NOTHROW); + RUN_TEST(_DIRECT_NOTHROW); + } + + SECTION("Direct Element Access - FailFast") + { + RUN_TYPED_TEST_FAILFAST(_TYPED_DIRECT_FAILFAST); + RUN_TEST(_DIRECT_FAILFAST); + } + +#ifdef WIL_ENABLE_EXCEPTIONS + SECTION("Direct Element Access - Exceptions") + { + RUN_TYPED_TEST(_TYPED_DIRECT); + RUN_TEST(_DIRECT); + } +#endif + + REQUIRE(TestComObject::ObjectCount() == 0); +} + +TEST_CASE("Safearray::AccessData", "[safearray]") +{ + SECTION("Access Data - No Throw") + { + RUN_TYPED_TEST_NOTHROW(_TYPED_ACCESSDATA_NOTHROW); + RUN_TEST(_ACCESSDATA_NOTHROW); + } + + SECTION("Access Data - FailFast") + { + RUN_TYPED_TEST_FAILFAST(_TYPED_ACCESSDATA_FAILFAST); + RUN_TEST(_ACCESSDATA_FAILFAST); + } + +#ifdef WIL_ENABLE_EXCEPTIONS + SECTION("Access Data - Exceptions") + { + RUN_TYPED_TEST(_TYPED_ACCESSDATA); + RUN_TEST(_ACCESSDATA); + } +#endif + + REQUIRE(TestComObject::ObjectCount() == 0); +} + +TEST_CASE("Safearray::Put/Get Specials", "[safearray]") +{ + SECTION("Special Direct Element Access - No Throw") + { + RUN_TEST_SPECIAL(_TYPED_DIRECT_SPECIAL_NOTHROW); + } + + SECTION("Special Direct Element Access - FailFast") + { + RUN_TEST_SPECIAL(_TYPED_DIRECT_SPECIAL_FAILFAST); + } + +#ifdef WIL_ENABLE_EXCEPTIONS + SECTION("Special Direct Element Access - Exceptions") + { + RUN_TEST_SPECIAL(_TYPED_DIRECT_SPECIAL); + RUN_TEST_SPECIAL(_TYPED_DIRECT_SPECIAL_GET); + } +#endif + + REQUIRE(TestComObject::ObjectCount() == 0); +} + +#endif \ No newline at end of file diff --git a/tests/app/CMakeLists.txt b/tests/app/CMakeLists.txt index e6d606c5..747a8d75 100644 --- a/tests/app/CMakeLists.txt +++ b/tests/app/CMakeLists.txt @@ -9,6 +9,7 @@ target_compile_definitions(witest.app PRIVATE target_sources(witest.app PRIVATE ${COMMON_SOURCES} + ${CMAKE_CURRENT_SOURCE_DIR}/../SafearrayTests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../StlTests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../UniqueWinRTEventTokenTests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../WinRTTests.cpp diff --git a/tests/cpplatest/CMakeLists.txt b/tests/cpplatest/CMakeLists.txt index 06088169..99edf084 100644 --- a/tests/cpplatest/CMakeLists.txt +++ b/tests/cpplatest/CMakeLists.txt @@ -31,6 +31,7 @@ target_sources(witest.cpplatest PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../CppWinRTTests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../CppWinRT20Tests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../CppWinRTAuthoringTests.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../SafearrayTests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../StlTests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../TokenHelpersTests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../UniqueWinRTEventTokenTests.cpp diff --git a/tests/noexcept/CMakeLists.txt b/tests/noexcept/CMakeLists.txt index b68b4467..d909f4a4 100644 --- a/tests/noexcept/CMakeLists.txt +++ b/tests/noexcept/CMakeLists.txt @@ -23,6 +23,7 @@ endif() target_sources(witest.noexcept PRIVATE ${COMMON_SOURCES} ${CMAKE_CURRENT_SOURCE_DIR}/../RegistryTests.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../SafearrayTests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../TokenHelpersTests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../UniqueWinRTEventTokenTests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../WatcherTests.cpp diff --git a/tests/normal/CMakeLists.txt b/tests/normal/CMakeLists.txt index 7b651c2f..d799ada8 100644 --- a/tests/normal/CMakeLists.txt +++ b/tests/normal/CMakeLists.txt @@ -6,6 +6,7 @@ target_precompile_headers(witest PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../pch.h) target_sources(witest PRIVATE ${COMMON_SOURCES} ${CMAKE_CURRENT_SOURCE_DIR}/../RegistryTests.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../SafearrayTests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../StlTests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../TokenHelpersTests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../UniqueWinRTEventTokenTests.cpp diff --git a/tests/win7/CMakeLists.txt b/tests/win7/CMakeLists.txt index 15445f9f..8b13b7d4 100644 --- a/tests/win7/CMakeLists.txt +++ b/tests/win7/CMakeLists.txt @@ -10,6 +10,7 @@ target_compile_definitions(witest.win7 PRIVATE target_sources(witest.win7 PRIVATE ${COMMON_SOURCES} ${CMAKE_CURRENT_SOURCE_DIR}/../RegistryTests.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../SafearrayTests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../StlTests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../TokenHelpersTests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../WatcherTests.cpp