pcsx2/3rdparty/winwil/include/wil/registry_helpers.h

1951 lines
78 KiB
C++

//*********************************************************
//
// 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.
//
//*********************************************************
//! @file
//! Helpers for iterating over keys and values in the registry.
#ifndef __WIL_REGISTRY_HELPERS_INCLUDED
#define __WIL_REGISTRY_HELPERS_INCLUDED
#if defined(_STRING_) || defined(_VECTOR_) || (defined(__cpp_lib_optional) && defined(_OPTIONAL_)) || defined(WIL_DOXYGEN)
#include <functional>
#include <iterator>
#endif
#include <stdint.h>
#include <Windows.h>
#include "resource.h"
#ifdef _KERNEL_MODE
#error This header is not supported in kernel-mode.
#endif
namespace wil
{
namespace reg
{
/**
* @brief Helper function to translate registry return values if the value was not found
* @param hr HRESULT to test from registry APIs
* @return boolean if the HRESULT indicates the registry value was not found
*/
constexpr bool is_registry_not_found(HRESULT hr) WI_NOEXCEPT
{
return (hr == __HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) || (hr == __HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND));
}
/**
* @brief Helper function to translate registry return values if the buffer was too small
* @param hr HRESULT to test from registry APIs
* @return boolean if the HRESULT indicates the buffer was too small for the value being read
*/
constexpr bool is_registry_buffer_too_small(HRESULT hr) WI_NOEXCEPT
{
return hr == __HRESULT_FROM_WIN32(ERROR_MORE_DATA);
}
// Access rights for opening registry keys. See https://learn.microsoft.com/en-us/windows/win32/sysinfo/registry-key-security-and-access-rights.
enum class key_access
{
// Open key for reading.
read,
// Open key for reading and writing. Equivalent to KEY_ALL_ACCESS.
readwrite,
};
/// @cond
namespace reg_view_details
{
constexpr DWORD get_value_flags_from_value_type(DWORD type) WI_NOEXCEPT
{
switch (type)
{
case REG_DWORD:
return RRF_RT_REG_DWORD;
case REG_QWORD:
return RRF_RT_REG_QWORD;
case REG_SZ:
return RRF_RT_REG_SZ | RRF_RT_REG_EXPAND_SZ | RRF_NOEXPAND;
case REG_EXPAND_SZ:
return RRF_RT_REG_SZ | RRF_RT_REG_EXPAND_SZ;
case REG_MULTI_SZ:
return RRF_RT_REG_MULTI_SZ;
case REG_BINARY:
return RRF_RT_REG_BINARY;
// the caller can directly specify their own flags if they need to
default:
return type;
}
}
constexpr DWORD get_access_flags(key_access access) WI_NOEXCEPT
{
switch (access)
{
case key_access::read:
return KEY_READ;
case key_access::readwrite:
return KEY_ALL_ACCESS;
}
FAIL_FAST();
RESULT_NORETURN_RESULT(0);
}
/**
* @brief A utility function that walks a contiguous wchar_t container looking for strings within a multi-string
* @tparam InputIt An iterator type that reference a container that holds wchar_t characters to translate into individual
* strings
* @tparam Fn A callback function to be called each time a string is found - given the [begin, end] iterators referencing
* the found string
* @param first An iterator referencing to the beginning of the target container (like a std::begin iterator)
* @param last An iterator referencing one-past-the-end of the target container (like a std::end iterator)
* @param func A callback function to be called each time a string is found - given the [begin, end] iterators referencing
* the found string
*/
template <class InputIt, class Fn>
void walk_multistring(const InputIt& first, const InputIt& last, Fn func)
{
auto current = first;
const auto end_iterator = last;
const auto last_null = (end_iterator - 1);
while (current != end_iterator)
{
// hand rolling ::std::find(current, end_iterator, L'\0');
// as this may be called when <algorithm> isn't available
auto next = current;
while (next != end_iterator && *next != L'\0')
{
++next;
}
if (next != end_iterator)
{
// don't add an empty string for the final 2nd-null-terminator
if (next != last_null)
{
// call the function provided with the [begin, end] pair referencing a string found
func(current, next);
}
current = next + 1;
}
else
{
current = next;
}
}
}
#if defined(_VECTOR_) && defined(_STRING_) && defined(WIL_ENABLE_EXCEPTIONS)
/**
* @brief A translation function taking iterators referencing std::wstring objects and returns a corresponding
* std::vector<wchar_t> to be written to a MULTI_SZ registry value. The translation follows the rules for how
* MULTI_SZ registry values should be formatted, notably how null characters should be embedded within the returned
* vector
* @tparam InputIt An iterator type that references a container that holds std::wstring objects to translate into a
* wchar_t buffer
* @param first An iterator referencing to the beginning of the target container (like a std::begin iterator)
* @param last An iterator referencing one-past-the-end of the target container (like a std::end iterator)
* @return A std::vector<wchar_t> with the raw wchar_t buffer of bytes prepared to write to a MULTI_SZ registry value
*/
template <class InputIt>
::std::vector<wchar_t> get_multistring_from_wstrings(const InputIt& first, const InputIt& last)
{
::std::vector<wchar_t> multistring;
if (first == last)
{
multistring.push_back(L'\0');
multistring.push_back(L'\0');
return multistring;
}
for (const auto& wstr : ::wil::make_range(first, last))
{
multistring.insert(multistring.end(), ::std::begin(wstr), ::std::end(wstr));
multistring.push_back(L'\0');
}
// double-null-terminate the last string
multistring.push_back(L'\0');
return multistring;
}
/**
* @brief A translation function taking iterators referencing wchar_t characters and returns extracted individual
* std::wstring objects. The translation follows the rules for how MULTI_SZ registry value can be formatted,
* notably with embedded null characters. Note that this conversion avoids returning empty std::wstring objects
* even though the input may contain contiguous null wchar_t values
* @tparam InputIt An iterator type that reference a container that holds wchar_t characters to translate into individual
* strings
* @param first An iterator referencing to the beginning of the target container (like a std::begin iterator)
* @param last An iterator referencing one-past-the-end of the target container (like a std::end iterator)
* @return A std::vector<std::wstring> of the extracted strings from the input container of wchar_t characters
*/
template <class InputIt>
::std::vector<::std::wstring> get_wstring_vector_from_multistring(const InputIt& first, const InputIt& last)
{
if (last - first < 3)
{
// it doesn't have the required 2 terminating null characters - return an empty string
return ::std::vector<::std::wstring>(1);
}
::std::vector<::std::wstring> strings;
walk_multistring(first, last, [&](const InputIt& string_first, const InputIt& string_last) {
strings.emplace_back(string_first, string_last);
});
return strings;
}
#endif // #if defined(_VECTOR_) && defined(_STRING_) && defined(WIL_ENABLE_EXCEPTIONS)
#if defined(__WIL_OBJBASE_H_)
template <size_t C>
void get_multistring_bytearray_from_strings_nothrow(const PCWSTR data[C], ::wil::unique_cotaskmem_array_ptr<BYTE>& multistring) WI_NOEXCEPT
{
constexpr uint8_t nullTermination[2]{0x00, 0x00};
size_t total_array_length_bytes = 0;
for (size_t i = 0; i < C; ++i)
{
total_array_length_bytes += wcslen(data[i]) * sizeof(wchar_t);
total_array_length_bytes += sizeof(wchar_t); // plus one for the null-terminator
}
total_array_length_bytes += sizeof(wchar_t); // plus one for the ending double-null-terminator
*multistring.addressof() = static_cast<uint8_t*>(::CoTaskMemAlloc(total_array_length_bytes));
if (!multistring.get())
{
multistring.reset();
return;
}
*multistring.size_address() = total_array_length_bytes;
size_t array_offset = 0;
for (size_t i = 0; i < C; ++i)
{
const auto string_length_bytes = wcslen(data[i]) * sizeof(wchar_t);
memcpy(multistring.get() + array_offset, data[i], string_length_bytes);
array_offset += string_length_bytes;
static_assert(sizeof(nullTermination) == sizeof(wchar_t), "null terminator must be a wchar");
memcpy(multistring.get() + array_offset, nullTermination, sizeof(nullTermination));
array_offset += sizeof(nullTermination);
}
// double-null-terminate the last string
memcpy(multistring.get() + array_offset, nullTermination, sizeof(nullTermination));
}
/**
* @brief A translation function taking iterators referencing wchar_t characters and returns extracted individual
* wil::unique_cotaskmem_string objects. The translation follows the rules for how MULTI_SZ registry value can be
* formatted, notably with embedded null characters. Note that this conversion avoids returning empty
* wil::unique_cotaskmem_string objects even though the input may contain contiguous null wchar_t values
* @tparam InputIt An iterator type that reference a container that holds wchar_t characters to translate into individual
* strings
* @param first An iterator referencing to the beginning of the target container (like a std::begin iterator)
* @param last An iterator referencing one-past-the-end of the target container (like a std::end iterator)
* @param cotaskmem_array The [out] wil::unique_cotaskmem_array_ptr<wil::unique_cotaskmem_string> to contain the array of
* strings. A wil::unique_cotaskmem_array_ptr<wil::unique_cotaskmem_string> of the extracted strings from the
* input container of wchar_t characters. An empty wil::unique_cotaskmem_array_ptr should be translated as out-of
* memory as there should always be at least one wil::unique_cotaskmem_string
*/
template <class InputIt>
void get_cotaskmemstring_array_from_multistring_nothrow(
const InputIt& first, const InputIt& last, ::wil::unique_cotaskmem_array_ptr<::wil::unique_cotaskmem_string>& cotaskmem_array) WI_NOEXCEPT
{
if (last - first < 3)
{
// it doesn't have the required 2 terminating null characters - return an empty string
*cotaskmem_array.addressof() = static_cast<PWSTR*>(::CoTaskMemAlloc(sizeof(PWSTR) * 1));
if (cotaskmem_array)
{
auto new_string = ::wil::make_cotaskmem_string_nothrow(L"");
if (new_string)
{
*cotaskmem_array.size_address() = 1;
cotaskmem_array[0] = new_string.release();
}
else
{
// oom will return an empty array
cotaskmem_array.reset();
}
}
else
{
// oom will return an empty array
cotaskmem_array.reset();
}
return;
}
// we must first count the # of strings for the array
size_t arraySize = 0;
walk_multistring(first, last, [&](const InputIt&, const InputIt&) {
++arraySize;
});
// allocate the array size necessary to hold all the unique_cotaskmem_strings
*cotaskmem_array.addressof() = static_cast<PWSTR*>(::CoTaskMemAlloc(sizeof(PWSTR) * arraySize));
if (!cotaskmem_array)
{
// oom will return an empty array
cotaskmem_array.reset();
return;
}
*cotaskmem_array.size_address() = arraySize;
ZeroMemory(cotaskmem_array.data(), sizeof(PWSTR) * arraySize);
size_t arrayOffset = 0;
walk_multistring(first, last, [&](const InputIt& string_first, const InputIt& string_last) {
FAIL_FAST_IF(arrayOffset >= arraySize);
const auto stringSize = string_last - string_first;
auto new_string = ::wil::make_cotaskmem_string_nothrow(&(*string_first), stringSize);
if (!new_string)
{
// oom will return an empty array
cotaskmem_array.reset();
return;
}
cotaskmem_array[arrayOffset] = new_string.release();
++arrayOffset;
});
}
#endif // #if defined(__WIL_OBJBASE_H_)
namespace reg_value_type_info
{
// supports_prepare_buffer is used to determine if the input buffer to read a registry value should be prepared
// before the first call to the registry read API
template <typename T>
constexpr bool supports_prepare_buffer() WI_NOEXCEPT
{
return false;
}
template <typename T>
HRESULT prepare_buffer(T&) WI_NOEXCEPT
{
// no-op in the default case
return S_OK;
}
// supports_resize_buffer_bytes is used to determine if the input buffer to read a registry value can be resized
// for those cases if the error from the registry read API indicates it needs a larger buffer
template <typename T>
constexpr bool supports_resize_buffer_bytes() WI_NOEXCEPT
{
return false;
}
template <typename T>
constexpr HRESULT resize_buffer_bytes(T&, DWORD) WI_NOEXCEPT
{
return E_NOTIMPL;
}
// supports_trim_buffer is used to determine if the input buffer to read a registry value must be trimmed
// after the registry read API has successfully written into the supplied buffer
// note that currently only std::wstring requires this as it cannot have embedded nulls
template <typename T>
constexpr bool supports_trim_buffer() WI_NOEXCEPT
{
return false;
}
// if called for a type that does not support trimming, will return a zero-length
template <typename T>
constexpr size_t trim_buffer(T&) WI_NOEXCEPT
{
return 0;
}
constexpr void* get_buffer(const int32_t& value) WI_NOEXCEPT
{
return const_cast<int32_t*>(&value);
}
constexpr DWORD get_buffer_size_bytes(int32_t) WI_NOEXCEPT
{
return static_cast<DWORD>(sizeof(int32_t));
}
constexpr void* get_buffer(const uint32_t& value) WI_NOEXCEPT
{
return const_cast<uint32_t*>(&value);
}
constexpr DWORD get_buffer_size_bytes(uint32_t) WI_NOEXCEPT
{
return static_cast<DWORD>(sizeof(uint32_t));
}
constexpr void* get_buffer(const long& value) WI_NOEXCEPT
{
return const_cast<long*>(&value);
}
constexpr DWORD get_buffer_size_bytes(long) WI_NOEXCEPT
{
return static_cast<DWORD>(sizeof(long));
}
constexpr void* get_buffer(const unsigned long& value) WI_NOEXCEPT
{
return const_cast<unsigned long*>(&value);
}
constexpr DWORD get_buffer_size_bytes(unsigned long) WI_NOEXCEPT
{
return static_cast<DWORD>(sizeof(unsigned long));
}
constexpr void* get_buffer(const int64_t& value) WI_NOEXCEPT
{
return const_cast<int64_t*>(&value);
}
constexpr DWORD get_buffer_size_bytes(int64_t) WI_NOEXCEPT
{
return static_cast<DWORD>(sizeof(int64_t));
}
constexpr void* get_buffer(const uint64_t& value) WI_NOEXCEPT
{
return const_cast<uint64_t*>(&value);
}
constexpr DWORD get_buffer_size_bytes(uint64_t) WI_NOEXCEPT
{
return static_cast<DWORD>(sizeof(uint64_t));
}
constexpr void* get_buffer(PCWSTR value) WI_NOEXCEPT
{
return const_cast<wchar_t*>(value);
}
inline DWORD get_buffer_size_bytes(PCWSTR value) WI_NOEXCEPT
{
if (!value)
{
return 0;
}
// including the last null buffer space in the returned buffer-size-bytes
// as the registry API we call guarantees null termination
return static_cast<DWORD>((::wcslen(value) + 1) * sizeof(wchar_t));
}
#if defined(_VECTOR_) && defined(WIL_ENABLE_EXCEPTIONS)
inline void* get_buffer(const ::std::vector<uint8_t>& buffer) WI_NOEXCEPT
{
return const_cast<uint8_t*>(buffer.data());
}
inline DWORD get_buffer_size_bytes(const ::std::vector<uint8_t>& value) WI_NOEXCEPT
{
return static_cast<DWORD>(value.size());
}
template <>
constexpr bool supports_prepare_buffer<::std::vector<uint8_t>>() WI_NOEXCEPT
{
return true;
}
inline HRESULT prepare_buffer(::std::vector<uint8_t>& value) WI_NOEXCEPT
try
{
// resize the initial vector to at least 1 byte
// this is needed so we can detect when the registry value exists
// but the value has zero-bytes
if (value.empty())
{
value.resize(1);
}
// zero out the buffer if pre-allocated
for (auto& string_char : value)
{
string_char = 0x00;
}
return S_OK;
}
CATCH_RETURN();
template <>
constexpr bool supports_resize_buffer_bytes<::std::vector<uint8_t>>() WI_NOEXCEPT
{
return true;
}
inline HRESULT resize_buffer_bytes(::std::vector<uint8_t>& buffer, DWORD byteSize) WI_NOEXCEPT
try
{
buffer.resize(byteSize);
return S_OK;
}
CATCH_RETURN();
// std::vector<wchar_t> does not implement resize_buffer_bytes
// because these support functions are only needed for set_value
// from the return of get_multistring_from_wstrings
inline void* get_buffer(const ::std::vector<wchar_t>& value) WI_NOEXCEPT
{
return const_cast<wchar_t*>(value.data());
}
inline DWORD get_buffer_size_bytes(const ::std::vector<wchar_t>& value) WI_NOEXCEPT
{
return static_cast<DWORD>(value.size()) * sizeof(wchar_t);
}
template <>
constexpr bool supports_prepare_buffer<::std::vector<wchar_t>>() WI_NOEXCEPT
{
return true;
}
inline HRESULT prepare_buffer(::std::vector<wchar_t>& value) WI_NOEXCEPT
{
// zero out the buffer if pre-allocated
for (auto& string_char : value)
{
string_char = L'\0';
}
return S_OK;
}
#endif // #if defined(_VECTOR_) && defined(WIL_ENABLE_EXCEPTIONS)
#if defined(_STRING_) && defined(WIL_ENABLE_EXCEPTIONS)
inline void* get_buffer(const ::std::wstring& string) WI_NOEXCEPT
{
return const_cast<wchar_t*>(string.data());
}
inline DWORD get_buffer_size_bytes(const ::std::wstring& string) WI_NOEXCEPT
{
// including the last null buffer space in the returned buffer-size-bytes
// as the registry API we call guarantees null termination
return static_cast<DWORD>((string.size() + 1) * sizeof(wchar_t));
}
template <>
constexpr bool supports_prepare_buffer<::std::wstring>() WI_NOEXCEPT
{
return true;
}
inline HRESULT prepare_buffer(::std::wstring& string) WI_NOEXCEPT
{
// zero out the buffer if pre-allocated
for (auto& string_char : string)
{
string_char = L'\0';
}
return S_OK;
}
template <>
constexpr bool supports_resize_buffer_bytes<::std::wstring>() WI_NOEXCEPT
{
return true;
}
inline HRESULT resize_buffer_bytes(::std::wstring& string, DWORD byteSize) WI_NOEXCEPT
try
{
string.resize(byteSize / sizeof(wchar_t));
return S_OK;
}
CATCH_RETURN();
template <>
constexpr bool supports_trim_buffer<::std::wstring>() WI_NOEXCEPT
{
return true;
}
inline size_t trim_buffer(::std::wstring& buffer) WI_NOEXCEPT
{
// remove any embedded null characters
const auto offset = buffer.find_first_of(L'\0');
if (offset != ::std::wstring::npos)
{
buffer.resize(offset);
}
return buffer.size();
}
#endif // #if defined(_STRING_) && defined(WIL_ENABLE_EXCEPTIONS)
#if defined(__WIL_OLEAUTO_H_)
inline void* get_buffer(const BSTR& value) WI_NOEXCEPT
{
return value;
}
inline DWORD get_buffer_size_bytes(const BSTR& value) WI_NOEXCEPT
{
auto length = ::SysStringLen(value);
if (length > 0)
{
// SysStringLen does not count the null-terminator
// including the last null buffer space in the returned buffer-size-bytes
// as the registry API we call guarantees null termination
length += 1;
}
return length * sizeof(WCHAR);
}
template <>
constexpr bool supports_prepare_buffer<BSTR>() WI_NOEXCEPT
{
return true;
}
inline HRESULT prepare_buffer(const BSTR& value) WI_NOEXCEPT
{
if (value)
{
// zero out the buffer if pre-allocated
for (auto& string_char : ::wil::make_range(value, get_buffer_size_bytes(value) / sizeof(WCHAR)))
{
string_char = L'\0';
}
}
return S_OK;
}
template <>
constexpr bool supports_resize_buffer_bytes<BSTR>() WI_NOEXCEPT
{
return true;
}
// transferringOwnership is only set to false if this is a 'shallow' copy of the BSTR
// and the caller maintained ownership of the original BSTR.
inline HRESULT resize_buffer_bytes(BSTR& string, DWORD byteSize, bool transferringOwnership = true) WI_NOEXCEPT
{
// copy the original BSTR to copy from later if needed
const BSTR original_string = string;
const DWORD original_string_length = string == nullptr ? 0 : ::SysStringLen(string);
// SysStringLen doesn't count the null-terminator, but our buffer size does
const bool original_string_length_too_small = (original_string_length + 1) < byteSize / sizeof(WCHAR);
if (original_string_length_too_small)
{
// pass a null BSTR value because SysAllocStringLen will copy the contents of the original BSTR,
// but in this case it's not long enough to copy the new length to be allocated
string = nullptr;
}
// convert bytes to length (number of WCHAR's)
DWORD length_to_alloc = byteSize / sizeof(WCHAR);
// SysAllocStringLen adds a null, so subtract a wchar_t from the input length
length_to_alloc = length_to_alloc > 0 ? length_to_alloc - 1 : length_to_alloc;
const BSTR new_bstr{::SysAllocStringLen(string, length_to_alloc)};
RETURN_IF_NULL_ALLOC(new_bstr);
// copy back the original BSTR if it was too small for SysAllocStringLen
// also assuming that both lengths are greater than zero
const DWORD sourceLengthToCopy = original_string_length < length_to_alloc ? original_string_length : length_to_alloc;
if (sourceLengthToCopy > 0 && original_string_length_too_small)
{
::memcpy_s(new_bstr, length_to_alloc * sizeof(WCHAR), original_string, sourceLengthToCopy * sizeof(WCHAR));
}
// if not transferring ownership, the caller will still own the original BSTR
if (transferringOwnership)
{
::SysFreeString(string);
}
string = new_bstr;
return S_OK;
}
inline void* get_buffer(const ::wil::unique_bstr& value) WI_NOEXCEPT
{
return value.get();
}
inline DWORD get_buffer_size_bytes(const ::wil::unique_bstr& value) WI_NOEXCEPT
{
return get_buffer_size_bytes(value.get());
}
template <>
constexpr bool supports_prepare_buffer<::wil::unique_bstr>() WI_NOEXCEPT
{
return true;
}
inline HRESULT prepare_buffer(const ::wil::unique_bstr& value) WI_NOEXCEPT
{
if (value)
{
// zero out the buffer if pre-allocated
for (auto& string_char : ::wil::make_range(value.get(), get_buffer_size_bytes(value) / sizeof(WCHAR)))
{
string_char = L'\0';
}
}
return S_OK;
}
template <>
constexpr bool supports_resize_buffer_bytes<::wil::unique_bstr>() WI_NOEXCEPT
{
return true;
}
inline HRESULT resize_buffer_bytes(::wil::unique_bstr& string, DWORD byteSize) WI_NOEXCEPT
{
BSTR temp_bstr = string.get();
// not transferring ownership of the BSTR within 'string' to resize_buffer_bytes()
// resize_buffer_bytes() will overwrite temp_bstr with a newly-allocated BSTR
constexpr bool transferringOwnership = false;
RETURN_IF_FAILED(resize_buffer_bytes(temp_bstr, byteSize, transferringOwnership));
// if succeeded in creating a new BSTR, move ownership of the new BSTR into string
string.reset(temp_bstr);
return S_OK;
}
#endif // #if defined(__WIL_OLEAUTO_H_)
#if defined(__WIL_OLEAUTO_H_STL)
inline void* get_buffer(const ::wil::shared_bstr& value) WI_NOEXCEPT
{
return value.get();
}
inline DWORD get_buffer_size_bytes(const ::wil::shared_bstr& value) WI_NOEXCEPT
{
return get_buffer_size_bytes(value.get());
}
template <>
constexpr bool supports_prepare_buffer<::wil::shared_bstr>() WI_NOEXCEPT
{
return true;
}
inline HRESULT prepare_buffer(const ::wil::shared_bstr& value) WI_NOEXCEPT
{
if (value)
{
// zero out the buffer if pre-allocated
for (auto& string_char : ::wil::make_range(value.get(), get_buffer_size_bytes(value) / sizeof(WCHAR)))
{
string_char = L'\0';
}
}
return S_OK;
}
template <>
constexpr bool supports_resize_buffer_bytes<::wil::shared_bstr>() WI_NOEXCEPT
{
return true;
}
inline HRESULT resize_buffer_bytes(::wil::shared_bstr& string, DWORD byteSize) WI_NOEXCEPT
{
BSTR temp_bstr = string.get();
// not transferring ownership of the BSTR within 'string' to resize_buffer_bytes()
// resize_buffer_bytes() will overwrite temp_bstr with a newly-allocated BSTR
constexpr bool transferringOwnership = false;
RETURN_IF_FAILED(resize_buffer_bytes(temp_bstr, byteSize, transferringOwnership));
// if succeeded in creating a new BSTR, move ownership of the new BSTR into string
string.reset(temp_bstr);
return S_OK;
}
#endif // #if defined(__WIL_OLEAUTO_H_STL)
#if defined(__WIL_OBJBASE_H_)
inline void* get_buffer(const ::wil::unique_cotaskmem_string& value) WI_NOEXCEPT
{
return value.get();
}
constexpr DWORD get_buffer_size_bytes(const ::wil::unique_cotaskmem_string&) WI_NOEXCEPT
{
// wil::unique_cotaskmem_string does not intrinsically track its internal buffer size
// thus the caller must track the buffer size it requested to be allocated
return 0;
}
template <>
constexpr bool supports_resize_buffer_bytes<::wil::unique_cotaskmem_string>() WI_NOEXCEPT
{
return true;
}
inline HRESULT resize_buffer_bytes(::wil::unique_cotaskmem_string& string, DWORD byteSize) WI_NOEXCEPT
{
// convert bytes to length (number of WCHAR's)
size_t length = byteSize / sizeof(wchar_t);
// ::wil::make_unique_string_nothrow adds one to the length when it allocates, so subtracting 1 from the input length
length = length > 0 ? length - 1 : length;
auto new_string = ::wil::make_unique_string_nothrow<::wil::unique_cotaskmem_string>(string.get(), length);
RETURN_IF_NULL_ALLOC(new_string.get());
string = ::wistd::move(new_string);
return S_OK;
}
inline void* get_buffer(const ::wil::unique_cotaskmem_array_ptr<uint8_t>& value) WI_NOEXCEPT
{
return value.get();
}
inline DWORD get_buffer_size_bytes(const ::wil::unique_cotaskmem_array_ptr<uint8_t>& value) WI_NOEXCEPT
{
return static_cast<DWORD>(value.size());
}
template <>
constexpr bool supports_resize_buffer_bytes<::wil::unique_cotaskmem_array_ptr<uint8_t>>() WI_NOEXCEPT
{
return true;
}
inline HRESULT resize_buffer_bytes(::wil::unique_cotaskmem_array_ptr<uint8_t>& arrayValue, DWORD byteSize) WI_NOEXCEPT
{
::wil::unique_cotaskmem_array_ptr<uint8_t> tempValue;
*tempValue.addressof() = static_cast<uint8_t*>(::CoTaskMemAlloc(byteSize));
RETURN_IF_NULL_ALLOC(tempValue.get());
*tempValue.size_address() = byteSize;
const auto bytesToCopy = arrayValue.size() < byteSize ? arrayValue.size() : byteSize;
CopyMemory(tempValue.get(), arrayValue.get(), bytesToCopy);
arrayValue = ::wistd::move(tempValue);
return S_OK;
}
#endif // #if defined(__WIL_OBJBASE_H_)
#if defined(__WIL_OBJBASE_H_STL)
inline void* get_buffer(const ::wil::shared_cotaskmem_string& value) WI_NOEXCEPT
{
return value.get();
}
constexpr DWORD get_buffer_size_bytes(const ::wil::shared_cotaskmem_string&) WI_NOEXCEPT
{
// wil::shared_cotaskmem_string does not intrinsically track its internal buffer size
// thus the caller must track the buffer size it requested to be allocated
return 0;
}
template <>
constexpr bool supports_resize_buffer_bytes<::wil::shared_cotaskmem_string>() WI_NOEXCEPT
{
return true;
}
inline HRESULT resize_buffer_bytes(::wil::shared_cotaskmem_string& string, DWORD byteSize) WI_NOEXCEPT
{
// convert bytes to length (number of WCHAR's)
size_t length = byteSize / sizeof(WCHAR);
// ::wil::make_unique_string_nothrow adds one to the length when it allocates, so subtracting 1 from the input length
length = length > 0 ? length - 1 : length;
auto new_string = ::wil::make_unique_string_nothrow<::wil::unique_cotaskmem_string>(string.get(), length);
RETURN_IF_NULL_ALLOC(new_string.get());
string = ::wistd::move(new_string);
return S_OK;
}
#endif // #if defined(__WIL_OBJBASE_H_STL)
inline void* get_buffer(const ::wil::unique_process_heap_string& value) WI_NOEXCEPT
{
return value.get();
}
constexpr DWORD get_buffer_size_bytes(const ::wil::unique_process_heap_string&) WI_NOEXCEPT
{
// wil::unique_process_heap_string does not intrinsically track its internal buffer size
// thus the caller must track the buffer size it requested to be allocated
return 0;
}
template <>
constexpr bool supports_resize_buffer_bytes<::wil::unique_process_heap_string>() WI_NOEXCEPT
{
return true;
}
inline HRESULT resize_buffer_bytes(::wil::unique_process_heap_string& string, DWORD byteSize) WI_NOEXCEPT
{
// convert bytes to length (number of WCHAR's)
size_t length = byteSize / sizeof(wchar_t);
// ::wil::make_unique_string_nothrow adds one to the length when it allocates, so subtracting 1 from the input length
length = length > 0 ? length - 1 : length;
auto new_string = ::wil::make_unique_string_nothrow<::wil::unique_process_heap_string>(string.get(), length);
RETURN_IF_NULL_ALLOC(new_string.get());
string = ::wistd::move(new_string);
return S_OK;
}
// constexpr expressions to determining the get* and set* registry value types
// for all supported types T to read/write values
template <typename T>
DWORD get_value_type() WI_NOEXCEPT
{
static_assert(sizeof(T) != sizeof(T), "Unsupported type for get_value_type");
}
template <typename T>
DWORD set_value_type() WI_NOEXCEPT
{
static_assert(sizeof(T) != sizeof(T), "Unsupported type for set_value_type");
}
template <>
constexpr DWORD get_value_type<int32_t>() WI_NOEXCEPT
{
return get_value_flags_from_value_type(REG_DWORD);
}
template <>
constexpr DWORD set_value_type<int32_t>() WI_NOEXCEPT
{
return REG_DWORD;
}
template <>
constexpr DWORD get_value_type<uint32_t>() WI_NOEXCEPT
{
return get_value_flags_from_value_type(REG_DWORD);
}
template <>
constexpr DWORD set_value_type<uint32_t>() WI_NOEXCEPT
{
return REG_DWORD;
}
template <>
constexpr DWORD get_value_type<long>() WI_NOEXCEPT
{
return get_value_flags_from_value_type(REG_DWORD);
}
template <>
constexpr DWORD set_value_type<long>() WI_NOEXCEPT
{
return REG_DWORD;
}
template <>
constexpr DWORD get_value_type<unsigned long>() WI_NOEXCEPT
{
return get_value_flags_from_value_type(REG_DWORD);
}
template <>
constexpr DWORD set_value_type<unsigned long>() WI_NOEXCEPT
{
return REG_DWORD;
}
template <>
constexpr DWORD get_value_type<int64_t>() WI_NOEXCEPT
{
return get_value_flags_from_value_type(REG_QWORD);
}
template <>
constexpr DWORD set_value_type<int64_t>() WI_NOEXCEPT
{
return REG_QWORD;
}
template <>
constexpr DWORD get_value_type<uint64_t>() WI_NOEXCEPT
{
return get_value_flags_from_value_type(REG_QWORD);
}
template <>
constexpr DWORD set_value_type<uint64_t>() WI_NOEXCEPT
{
return REG_QWORD;
}
template <>
constexpr DWORD get_value_type<PCWSTR>() WI_NOEXCEPT
{
return get_value_flags_from_value_type(REG_SZ);
}
template <>
constexpr DWORD set_value_type<PCWSTR>() WI_NOEXCEPT
{
return REG_SZ;
}
#if defined(_STRING_) && defined(WIL_ENABLE_EXCEPTIONS)
template <>
constexpr DWORD get_value_type<::std::wstring>() WI_NOEXCEPT
{
return get_value_flags_from_value_type(REG_SZ);
}
template <>
constexpr DWORD set_value_type<const ::std::wstring>() WI_NOEXCEPT
{
return REG_SZ;
}
#endif // #if defined(_STRING_) && defined(WIL_ENABLE_EXCEPTIONS)
#if defined(__WIL_OLEAUTO_H_)
template <>
constexpr DWORD get_value_type<BSTR>() WI_NOEXCEPT
{
return get_value_flags_from_value_type(REG_SZ);
}
template <>
constexpr DWORD get_value_type<::wil::unique_bstr>() WI_NOEXCEPT
{
return get_value_flags_from_value_type(REG_SZ);
}
template <>
constexpr DWORD set_value_type<const BSTR>() WI_NOEXCEPT
{
return REG_SZ;
}
template <>
constexpr DWORD set_value_type<const ::wil::unique_bstr>() WI_NOEXCEPT
{
return REG_SZ;
}
#endif // #if defined(__WIL_OLEAUTO_H_)
#if defined(__WIL_OLEAUTO_H_STL)
template <>
constexpr DWORD get_value_type<::wil::shared_bstr>() WI_NOEXCEPT
{
return get_value_flags_from_value_type(REG_SZ);
}
template <>
constexpr DWORD set_value_type<const ::wil::shared_bstr>() WI_NOEXCEPT
{
return REG_SZ;
}
#endif // #if defined(__WIL_OLEAUTO_H_STL)
#if defined(__WIL_OBJBASE_H_)
template <>
constexpr DWORD get_value_type<::wil::unique_cotaskmem_string>() WI_NOEXCEPT
{
return get_value_flags_from_value_type(REG_SZ);
}
template <>
constexpr DWORD set_value_type<const ::wil::unique_cotaskmem_string>() WI_NOEXCEPT
{
return REG_SZ;
}
#endif // defined(__WIL_OBJBASE_H_)
#if defined(__WIL_OBJBASE_H_STL)
template <>
constexpr DWORD get_value_type<::wil::shared_cotaskmem_string>() WI_NOEXCEPT
{
return get_value_flags_from_value_type(REG_SZ);
}
template <>
constexpr DWORD set_value_type<const ::wil::shared_cotaskmem_string>() WI_NOEXCEPT
{
return REG_SZ;
}
#endif // #if defined(__WIL_OBJBASE_H_STL)
} // namespace reg_value_type_info
template <typename err_policy = ::wil::err_exception_policy>
class reg_view_t
{
public:
explicit reg_view_t(HKEY key) WI_NOEXCEPT : m_key(key)
{
}
~reg_view_t() WI_NOEXCEPT = default;
reg_view_t(const reg_view_t&) = delete;
reg_view_t& operator=(const reg_view_t&) = delete;
reg_view_t(reg_view_t&&) = delete;
reg_view_t& operator=(reg_view_t&&) = delete;
typename err_policy::result open_key(
_In_opt_ _In_opt_ PCWSTR subKey, _Out_ HKEY* hkey, ::wil::reg::key_access access = ::wil::reg::key_access::read) const
{
constexpr DWORD zero_options{0};
return err_policy::HResult(
HRESULT_FROM_WIN32(::RegOpenKeyExW(m_key, subKey, zero_options, get_access_flags(access), hkey)));
}
typename err_policy::result create_key(PCWSTR subKey, _Out_ HKEY* hkey, ::wil::reg::key_access access = ::wil::reg::key_access::read) const
{
*hkey = nullptr;
constexpr DWORD zero_reserved{0};
constexpr PWSTR null_class{nullptr};
constexpr DWORD zero_options{0};
constexpr SECURITY_ATTRIBUTES* null_security_attributes{nullptr};
DWORD disposition{0};
return err_policy::HResult(HRESULT_FROM_WIN32(::RegCreateKeyExW(
m_key, subKey, zero_reserved, null_class, zero_options, get_access_flags(access), null_security_attributes, hkey, &disposition)));
}
typename err_policy::result delete_tree(_In_opt_ PCWSTR sub_key) const
{
auto hr = HRESULT_FROM_WIN32(::RegDeleteTreeW(m_key, sub_key));
if (::wil::reg::is_registry_not_found(hr))
{
hr = S_OK;
}
return err_policy::HResult(hr);
}
typename err_policy::result delete_value(_In_opt_ PCWSTR value_name) const
{
return err_policy::HResult(HRESULT_FROM_WIN32(::RegDeleteValueW(m_key, value_name)));
}
template <typename R>
typename err_policy::result get_value(
_In_opt_ PCWSTR subkey, _In_opt_ PCWSTR value_name, R& return_value, DWORD type = reg_value_type_info::get_value_type<R>()) const
{
return get_value_with_type(subkey, value_name, return_value, type);
}
// typename D supports unsigned 32-bit values; i.e. allows the caller to pass a DWORD* as well as uint32_t*
template <size_t Length, typename DwordType, wistd::enable_if_t<wistd::is_same_v<DwordType, uint32_t> || wistd::is_same_v<DwordType, unsigned long>>* = nullptr>
typename err_policy::result get_value_char_array(
_In_opt_ PCWSTR subkey, _In_opt_ PCWSTR value_name, WCHAR (&return_value)[Length], DWORD type, _Out_opt_ DwordType* requiredBytes) const
{
constexpr DwordType zero_value{0ul};
::wil::assign_to_opt_param(requiredBytes, zero_value);
DWORD data_size_bytes{Length * sizeof(WCHAR)};
const auto hr = HRESULT_FROM_WIN32(::RegGetValueW(
m_key, subkey, value_name, ::wil::reg::reg_view_details::get_value_flags_from_value_type(type), nullptr, return_value, &data_size_bytes));
if (SUCCEEDED(hr) || ::wil::reg::is_registry_buffer_too_small(hr))
{
const DwordType updated_value{data_size_bytes};
::wil::assign_to_opt_param(requiredBytes, updated_value);
}
return err_policy::HResult(hr);
}
#if defined(_OPTIONAL_) && defined(__cpp_lib_optional)
// intended for err_exception_policy as err_returncode_policy will not get an error code
template <typename R>
::std::optional<R> try_get_value(
_In_opt_ PCWSTR subkey, _In_opt_ PCWSTR value_name, DWORD type = reg_value_type_info::get_value_type<R>()) const
{
R value{};
const auto hr = get_value_with_type<R, ::wil::err_returncode_policy>(subkey, value_name, value, type);
if (SUCCEEDED(hr))
{
return ::std::optional(::wistd::move(value));
}
if (::wil::reg::is_registry_not_found(hr))
{
return ::std::nullopt;
}
// throw if exception policy
err_policy::HResult(hr);
return ::std::nullopt;
}
#endif // #if defined (_OPTIONAL_) && defined(__cpp_lib_optional)
template <typename R>
typename err_policy::result set_value(
_In_opt_ PCWSTR subkey, _In_opt_ PCWSTR value_name, const R& value, DWORD type = reg_value_type_info::set_value_type<R>()) const
{
return set_value_with_type(subkey, value_name, value, type);
}
private:
const HKEY m_key{};
template <typename R>
typename err_policy::result set_value_with_type(_In_opt_ PCWSTR subkey, _In_opt_ PCWSTR value_name, const R& value, DWORD type) const
{
return err_policy::HResult(HRESULT_FROM_WIN32(::RegSetKeyValueW(
m_key,
subkey,
value_name,
type,
static_cast<uint8_t*>(reg_value_type_info::get_buffer(value)),
reg_value_type_info::get_buffer_size_bytes(value))));
}
template <typename R, typename get_value_with_type_policy = err_policy>
typename get_value_with_type_policy::result get_value_with_type(
_In_opt_ PCWSTR subkey, _In_opt_ PCWSTR value_name, R& return_value, DWORD type = reg_value_type_info::get_value_type<R>()) const
{
if
#if defined(__cpp_if_constexpr)
constexpr
#endif
(reg_value_type_info::supports_prepare_buffer<R>())
{
const auto prepare_buffer_hr = reg_value_type_info::prepare_buffer(return_value);
if (FAILED(prepare_buffer_hr))
{
return get_value_with_type_policy::HResult(prepare_buffer_hr);
}
}
// get_buffer_size_bytes should include the null terminator when used for strings.
DWORD bytes_allocated{reg_value_type_info::get_buffer_size_bytes(return_value)};
HRESULT get_value_hresult = S_OK;
for (;;)
{
constexpr DWORD* null_type{nullptr};
DWORD data_size_bytes{bytes_allocated};
get_value_hresult = HRESULT_FROM_WIN32(::RegGetValueW(
m_key,
subkey,
value_name,
get_value_flags_from_value_type(type),
null_type,
reg_value_type_info::get_buffer(return_value),
&data_size_bytes));
// some return types we can grow as needed - e.g. when writing to a std::wstring
// only compile and resize_buffer for those types that support dynamically growing the buffer
if
#if defined(__cpp_if_constexpr)
constexpr
#endif
(reg_value_type_info::supports_resize_buffer_bytes<R>())
{
// Attempt to grow the buffer with the data_size_bytes returned from GetRegValueW
// GetRegValueW will indicate the caller allocate the returned number of bytes in one of two cases:
// 1. returns ERROR_MORE_DATA
// 2. returns ERROR_SUCCESS when we gave it a nullptr for the out buffer
const bool shouldReallocate =
(::wil::reg::is_registry_buffer_too_small(get_value_hresult)) ||
(SUCCEEDED(get_value_hresult) && (reg_value_type_info::get_buffer(return_value) == nullptr) &&
(data_size_bytes > 0));
if (shouldReallocate)
{
// verify if resize_buffer succeeded allocation
const auto resize_buffer_hr = reg_value_type_info::resize_buffer_bytes(return_value, data_size_bytes);
if (FAILED(resize_buffer_hr))
{
// if resize fails, return this error back to the caller
return get_value_with_type_policy::HResult(resize_buffer_hr);
}
// if it resize succeeds, continue the for loop to try again
bytes_allocated = data_size_bytes;
continue;
}
// if the RegGetValueW call succeeded with a non-null [out] param,
// and the type supports resize_buffer_bytes
// and the bytes we allocated don't match data_size_bytes returned from RegGetValueW
// resize the buffer to match what RegGetValueW returned
if (SUCCEEDED(get_value_hresult))
{
const auto current_byte_size = reg_value_type_info::get_buffer_size_bytes(return_value);
if (current_byte_size != data_size_bytes)
{
// verify if resize_buffer_bytes succeeded allocation
const auto resize_buffer_hr = reg_value_type_info::resize_buffer_bytes(return_value, data_size_bytes);
if (FAILED(resize_buffer_hr))
{
// if resize fails, return this error back to the caller
return get_value_with_type_policy::HResult(resize_buffer_hr);
}
}
}
}
// we don't need to reallocate and retry the call to RegGetValueW so breaking out of the loop
break;
}
// some types (generally string types) require trimming its internal buffer after RegGetValueW successfully wrote into its buffer
if
#if defined(__cpp_if_constexpr)
constexpr
#endif
(reg_value_type_info::supports_trim_buffer<R>())
{
if (SUCCEEDED(get_value_hresult))
{
reg_value_type_info::trim_buffer(return_value);
}
}
return get_value_with_type_policy::HResult(get_value_hresult);
}
};
using reg_view_nothrow = ::wil::reg::reg_view_details::reg_view_t<::wil::err_returncode_policy>;
#if defined(WIL_ENABLE_EXCEPTIONS)
using reg_view = ::wil::reg::reg_view_details::reg_view_t<::wil::err_exception_policy>;
#endif // #if defined(WIL_ENABLE_EXCEPTIONS)
} // namespace reg_view_details
/// @endcond
/// @cond
namespace reg_iterator_details
{
constexpr uint32_t iterator_end_offset = 0xffffffff;
constexpr size_t iterator_default_buffer_length = 32;
constexpr size_t iterator_max_keyname_length = 255;
constexpr size_t iterator_max_valuename_length = 16383;
// function overloads to allow *_enumerator objects to be constructed from all 3 types of HKEY representatives
inline HKEY get_hkey(HKEY h) WI_NOEXCEPT
{
return h;
}
inline HKEY get_hkey(const ::wil::unique_hkey& h) WI_NOEXCEPT
{
return h.get();
}
#if defined(__WIL_WINREG_STL)
inline HKEY get_hkey(const ::wil::shared_hkey& h) WI_NOEXCEPT
{
return h.get();
}
#endif // #if defined(__WIL_WINREG_STL)
#if defined(WIL_ENABLE_EXCEPTIONS) && defined(_STRING_)
// overloads for some of the below string functions - specific for std::wstring
// these overloads must be declared before the template functions below, as some of those template functions
// reference these overload functions
inline void clear_name(::std::wstring& name, size_t) WI_NOEXCEPT
{
name.assign(name.size(), L'\0');
}
inline ::std::wstring copy_name(const ::std::wstring& str, size_t length) WI_NOEXCEPT
{
try
{
// guarantee that the copied string has the specified internal length
// i.e., the same length assumptions hold when the string is copied
::std::wstring tempString(length, L'0');
tempString.assign(str);
return tempString;
}
catch (...)
{
return {};
}
}
inline bool can_derive_length(const ::std::wstring& name) WI_NOEXCEPT
{
return !name.empty();
}
#endif // #if defined(WIL_ENABLE_EXCEPTIONS) && defined(_STRING_)
// string manipulation functions needed for iterator functions
template <typename T>
PWSTR address_of_name(const T& name) WI_NOEXCEPT
{
return static_cast<PWSTR>(::wil::reg::reg_view_details::reg_value_type_info::get_buffer(name));
}
template <typename T>
bool can_derive_length(const T& name) WI_NOEXCEPT
{
return static_cast<bool>(address_of_name(name));
}
template <typename T>
bool compare_name(const T& name, PCWSTR comparand) WI_NOEXCEPT
{
if (!can_derive_length(name) || !comparand)
{
return false;
}
return 0 == wcscmp(address_of_name(name), comparand);
}
template <typename T>
void clear_name(const T& name, size_t length) WI_NOEXCEPT
{
if (can_derive_length(name) && length > 0)
{
memset(address_of_name(name), 0, length * sizeof(wchar_t));
}
}
// failure returns zero
template <typename T>
size_t resize_name_cch(T& name, size_t current_length, size_t new_length) WI_NOEXCEPT
{
if (new_length > current_length)
{
if (FAILED(::wil::reg::reg_view_details::reg_value_type_info::resize_buffer_bytes(
name, static_cast<DWORD>(new_length * sizeof(wchar_t)))))
{
return 0;
}
current_length = new_length;
// fall through to clear the newly allocated buffer
// and return the new length
}
// continue to use the existing buffer since the requested length is less than or equals to the current length
clear_name(name, current_length);
return current_length;
}
template <typename T>
T copy_name(const T& name, size_t length) WI_NOEXCEPT
{
if (!can_derive_length(name))
{
return {};
}
return ::wil::make_unique_string_nothrow<T>(address_of_name(name), length);
}
#if defined(__WIL_OLEAUTO_H_)
inline ::wil::unique_bstr copy_name(const ::wil::unique_bstr& name, size_t length) WI_NOEXCEPT
{
if (!can_derive_length(name))
{
return {};
}
// SysAllocStringLen adds a null, so subtract a wchar_t from the input length
length = length > 0 ? length - 1 : length;
return ::wil::unique_bstr{::SysAllocStringLen(name.get(), static_cast<UINT>(length))};
}
#endif // #if defined(__WIL_OLEAUTO_H_)
} // namespace reg_iterator_details
/// @endcond
// forward declaration to allow friend-ing the template iterator class
#if defined(WIL_ENABLE_EXCEPTIONS)
template <typename T>
class iterator_t;
#endif
template <typename T>
class iterator_nothrow_t;
// all methods must be noexcept - to be usable with any iterator type (throwing or non-throwing)
template <typename T>
class key_iterator_data
{
public:
T name{};
key_iterator_data(HKEY key = nullptr) WI_NOEXCEPT : m_hkey{key}
{
}
~key_iterator_data() WI_NOEXCEPT = default;
key_iterator_data(const key_iterator_data& rhs) WI_NOEXCEPT
{
// might return null/empty string on failure
name = ::wil::reg::reg_iterator_details::copy_name(rhs.name, rhs.m_name_length);
m_hkey = rhs.m_hkey;
m_index = rhs.m_index;
m_name_length = ::wil::reg::reg_iterator_details::can_derive_length(name) ? rhs.m_name_length : 0;
}
key_iterator_data& operator=(const key_iterator_data& rhs) WI_NOEXCEPT
{
if (&rhs != this)
{
key_iterator_data temp(rhs);
*this = ::wistd::move(temp);
}
return *this;
}
key_iterator_data(key_iterator_data&& rhs) WI_NOEXCEPT : name{wistd::move(rhs.name)},
m_hkey{wistd::move(rhs.m_hkey)},
m_index{wistd::move(rhs.m_index)},
m_name_length{wistd::move(rhs.m_name_length)}
{
// once name is moved, we must reset m_name_length as name is now empty
rhs.m_name_length = 0;
}
key_iterator_data& operator=(key_iterator_data&& rhs) WI_NOEXCEPT
{
if (&rhs != this)
{
name = ::wistd::move(rhs.name);
m_hkey = ::wistd::move(rhs.m_hkey);
m_index = ::wistd::move(rhs.m_index);
m_name_length = ::wistd::move(rhs.m_name_length);
// once name is moved, we must reset m_name_length as name is now empty
rhs.m_name_length = 0;
}
return *this;
}
// Case-sensitive comparison
bool operator==(PCWSTR comparand) const WI_NOEXCEPT
{
return ::wil::reg::reg_iterator_details::compare_name(name, comparand);
}
private:
#if defined(WIL_ENABLE_EXCEPTIONS)
friend class ::wil::reg::iterator_t<key_iterator_data>;
#endif
friend class ::wil::reg::iterator_nothrow_t<key_iterator_data>;
bool at_end() const WI_NOEXCEPT
{
return m_index == ::wil::reg::reg_iterator_details::iterator_end_offset;
}
void make_end_iterator() WI_NOEXCEPT
{
::wil::reg::reg_iterator_details::clear_name(name, m_name_length);
m_index = ::wil::reg::reg_iterator_details::iterator_end_offset;
}
bool resize(size_t new_length) WI_NOEXCEPT
{
// if resize fails, will return 0
m_name_length = ::wil::reg::reg_iterator_details::resize_name_cch(name, m_name_length, new_length);
return m_name_length > 0;
}
HRESULT enumerate_current_index() WI_NOEXCEPT
{
FAIL_FAST_IF(at_end());
auto string_length = static_cast<DWORD>(m_name_length) > 0
? static_cast<DWORD>(m_name_length)
: static_cast<DWORD>(::wil::reg::reg_iterator_details::iterator_default_buffer_length);
for (;;)
{
if (!resize(string_length))
{
RETURN_HR(E_OUTOFMEMORY);
}
const auto error = ::RegEnumKeyExW(
m_hkey, // hKey
m_index, // dwIndex
string_length == 0 ? nullptr : ::wil::reg::reg_iterator_details::address_of_name(name), // lpName
&string_length, // lpcchName
nullptr, // lpReserved
nullptr, // lpClass
nullptr, // lpcchClass
nullptr); // lpftLastWriteTime
if (error == ERROR_SUCCESS)
{
// string_length returned from RegEnumKeyExW does not include the null terminator
++string_length;
// if the string type supports resize, we must resize it to the returned string size
// to ensure continuity of the string type with the actual string size
if (::wil::reg::reg_view_details::reg_value_type_info::supports_resize_buffer_bytes<T>())
{
RETURN_IF_FAILED(::wil::reg::reg_view_details::reg_value_type_info::resize_buffer_bytes(
name, string_length * sizeof(WCHAR)));
m_name_length = string_length;
}
if (::wil::reg::reg_view_details::reg_value_type_info::supports_trim_buffer<T>())
{
m_name_length = ::wil::reg::reg_view_details::reg_value_type_info::trim_buffer(name);
}
break;
}
if (error == ERROR_NO_MORE_ITEMS)
{
make_end_iterator();
break;
}
if (error == ERROR_MORE_DATA)
{
// if our default wasn't big enough, resize to either half max or max length and try again
if (string_length < reg_iterator_details::iterator_max_keyname_length / 2)
{
string_length = reg_iterator_details::iterator_max_keyname_length / 2;
}
else if (string_length < reg_iterator_details::iterator_max_keyname_length + 1)
{
string_length = reg_iterator_details::iterator_max_keyname_length + 1; // plus one for null
}
else
{
// if we're already at max length, we can't grow anymore, so we'll just return the error
break;
}
continue;
}
// any other error will fail
RETURN_WIN32(error);
}
return S_OK;
}
HKEY m_hkey{};
uint32_t m_index = ::wil::reg::reg_iterator_details::iterator_end_offset;
size_t m_name_length{};
};
// all methods must be noexcept - to be usable with any iterator type (throwing or non-throwing)
template <typename T>
class value_iterator_data
{
public:
T name{};
DWORD type = REG_NONE;
value_iterator_data(HKEY key = nullptr) WI_NOEXCEPT : m_hkey{key}
{
}
~value_iterator_data() WI_NOEXCEPT = default;
value_iterator_data(const value_iterator_data& rhs) WI_NOEXCEPT
{
// might return null/empty string on failure
name = ::wil::reg::reg_iterator_details::copy_name(rhs.name, rhs.m_name_length);
type = rhs.type;
m_hkey = rhs.m_hkey;
m_index = rhs.m_index;
m_name_length = ::wil::reg::reg_iterator_details::can_derive_length(name) ? rhs.m_name_length : 0;
}
value_iterator_data& operator=(const value_iterator_data& rhs) WI_NOEXCEPT
{
if (&rhs != this)
{
value_iterator_data temp(rhs);
*this = ::wistd::move(temp);
}
return *this;
}
value_iterator_data(value_iterator_data&&) WI_NOEXCEPT = default;
value_iterator_data& operator=(value_iterator_data&& rhs) WI_NOEXCEPT = default;
bool at_end() const WI_NOEXCEPT
{
return m_index == ::wil::reg::reg_iterator_details::iterator_end_offset;
}
private:
#if defined(WIL_ENABLE_EXCEPTIONS)
friend class ::wil::reg::iterator_t<value_iterator_data>;
#endif
friend class ::wil::reg::iterator_nothrow_t<value_iterator_data>;
void make_end_iterator() WI_NOEXCEPT
{
::wil::reg::reg_iterator_details::clear_name(name, m_name_length);
m_index = ::wil::reg::reg_iterator_details::iterator_end_offset;
}
bool resize(size_t new_length)
{
// if resize fails, will return 0
m_name_length = ::wil::reg::reg_iterator_details::resize_name_cch(name, m_name_length, new_length);
return m_name_length > 0;
}
HRESULT enumerate_current_index() WI_NOEXCEPT
{
FAIL_FAST_IF(at_end());
auto string_length = static_cast<DWORD>(m_name_length) > 0
? static_cast<DWORD>(m_name_length)
: static_cast<DWORD>(::wil::reg::reg_iterator_details::iterator_default_buffer_length);
for (;;)
{
if (!resize(string_length))
{
RETURN_HR(E_OUTOFMEMORY);
}
const auto error = ::RegEnumValueW(
m_hkey, // hKey
m_index, // dwIndex
string_length == 0 ? nullptr : ::wil::reg::reg_iterator_details::address_of_name(name), // lpValueName
&string_length, // lpcchValueName
nullptr, // lpReserved
&type, // lpType
nullptr, // lpData
nullptr); // lpcbData
if (error == ERROR_SUCCESS)
{
// string_length returned from RegEnumValueW does not include the null terminator
++string_length;
// if the string type supports resize, we must resize it to the returned string size
// to ensure continuity of the string type with the actual string size
if (::wil::reg::reg_view_details::reg_value_type_info::supports_resize_buffer_bytes<T>())
{
RETURN_IF_FAILED(::wil::reg::reg_view_details::reg_value_type_info::resize_buffer_bytes(
name, string_length * sizeof(WCHAR)));
m_name_length = string_length;
}
if (::wil::reg::reg_view_details::reg_value_type_info::supports_trim_buffer<T>())
{
m_name_length = ::wil::reg::reg_view_details::reg_value_type_info::trim_buffer(name);
}
break;
}
if (error == ERROR_NO_MORE_ITEMS)
{
make_end_iterator();
break;
}
if (error == ERROR_MORE_DATA)
{
if (string_length == ::wil::reg::reg_iterator_details::iterator_max_valuename_length + 1)
{
// this is the max size, so we can't grow anymore, so we'll just return the error
break;
}
// resize and try again - growing exponentially up to the max
string_length *= 2;
if (string_length > ::wil::reg::reg_iterator_details::iterator_max_valuename_length + 1)
{
string_length = ::wil::reg::reg_iterator_details::iterator_max_valuename_length + 1;
}
continue;
}
// any other error will fail
RETURN_WIN32(error);
}
return S_OK;
}
HKEY m_hkey{};
uint32_t m_index = ::wil::reg::reg_iterator_details::iterator_end_offset;
size_t m_name_length{};
};
#if defined(WIL_ENABLE_EXCEPTIONS)
template <typename T>
class iterator_t
{
public:
// defining iterator_traits allows STL <algorithm> functions to be used with this iterator class.
// Notice this is a forward_iterator
// - does not support random-access (e.g. vector::iterator)
// - does not support bidirectional access (e.g. list::iterator)
#if defined(_ITERATOR_) || defined(WIL_DOXYGEN)
using iterator_category = ::std::forward_iterator_tag;
#endif
using value_type = T;
using difference_type = size_t;
using distance_type = size_t;
using pointer = T*;
using reference = T&;
iterator_t() WI_NOEXCEPT = default;
~iterator_t() WI_NOEXCEPT = default;
iterator_t(HKEY hkey) : m_data(hkey)
{
if (hkey != nullptr)
{
m_data.m_index = 0;
THROW_IF_FAILED(m_data.enumerate_current_index());
}
}
iterator_t(const iterator_t&) = default;
iterator_t& operator=(const iterator_t&) = default;
iterator_t(iterator_t&&) WI_NOEXCEPT = default;
iterator_t& operator=(iterator_t&&) WI_NOEXCEPT = default;
// operator support
const T& operator*() const
{
FAIL_FAST_IF(m_data.at_end());
return m_data;
}
const T& operator*()
{
FAIL_FAST_IF(m_data.at_end());
return m_data;
}
const T* operator->() const
{
FAIL_FAST_IF(m_data.at_end());
return &m_data;
}
const T* operator->()
{
FAIL_FAST_IF(m_data.at_end());
return &m_data;
}
bool operator==(const iterator_t& rhs) const WI_NOEXCEPT
{
if (m_data.at_end() || rhs.m_data.at_end())
{
// if either is not initialized (or end), both must not be initialized (or end) to be equal
return m_data.m_index == rhs.m_data.m_index;
}
return m_data.m_hkey == rhs.m_data.m_hkey && m_data.m_index == rhs.m_data.m_index;
}
bool operator!=(const iterator_t& rhs) const WI_NOEXCEPT
{
return !(*this == rhs);
}
// pre-increment
iterator_t& operator++()
{
this->operator+=(1);
return *this;
}
const iterator_t& operator++() const
{
this->operator+=(1);
return *this;
}
// increment by integer
iterator_t& operator+=(size_t offset)
{
uint32_t newIndex = m_data.m_index + static_cast<uint32_t>(offset);
if (newIndex < m_data.m_index)
{
// fail on integer overflow
THROW_HR(E_INVALIDARG);
}
if (newIndex == ::wil::reg::reg_iterator_details::iterator_end_offset)
{
// fail if this creates an end iterator
THROW_HR(E_INVALIDARG);
}
// iterate by the integer offset
for (size_t count = 0; count < offset; ++count)
{
++m_data.m_index;
THROW_IF_FAILED(m_data.enumerate_current_index());
}
return *this;
}
// not supporting post-increment - which would require copy-construction
iterator_t operator++(int) = delete;
private:
// container based on the class template type
T m_data{};
};
#endif
template <typename T>
class iterator_nothrow_t
{
public:
iterator_nothrow_t() WI_NOEXCEPT = default;
~iterator_nothrow_t() WI_NOEXCEPT = default;
iterator_nothrow_t(HKEY hkey) WI_NOEXCEPT : m_data(hkey)
{
if (hkey != nullptr)
{
m_data.m_index = 0;
m_last_error = m_data.enumerate_current_index();
}
}
iterator_nothrow_t(const iterator_nothrow_t&) WI_NOEXCEPT = default;
iterator_nothrow_t& operator=(const iterator_nothrow_t&) WI_NOEXCEPT = default;
iterator_nothrow_t(iterator_nothrow_t&&) WI_NOEXCEPT = default;
iterator_nothrow_t& operator=(iterator_nothrow_t&&) WI_NOEXCEPT = default;
bool at_end() const WI_NOEXCEPT
{
return m_data.at_end();
}
HRESULT last_error() const WI_NOEXCEPT
{
return m_last_error;
}
HRESULT move_next() WI_NOEXCEPT
{
const auto newIndex = m_data.m_index + 1;
if (newIndex < m_data.m_index)
{
// fail on integer overflow
m_last_error = E_INVALIDARG;
}
else if (newIndex == ::wil::reg::reg_iterator_details::iterator_end_offset)
{
// fail if this creates an end iterator
m_last_error = E_INVALIDARG;
}
else
{
m_data.m_index = newIndex;
m_last_error = m_data.enumerate_current_index();
}
if (FAILED(m_last_error))
{
// on failure, set the iterator to an end iterator
m_data.make_end_iterator();
}
return m_last_error;
}
// operator support
const T& operator*() const WI_NOEXCEPT
{
return m_data;
}
const T& operator*() WI_NOEXCEPT
{
return m_data;
}
const T* operator->() const WI_NOEXCEPT
{
return &m_data;
}
const T* operator->() WI_NOEXCEPT
{
return &m_data;
}
bool operator==(const iterator_nothrow_t& rhs) const WI_NOEXCEPT
{
if (m_data.at_end() || rhs.m_data.at_end())
{
// if either is not initialized (or end), both must not be initialized (or end) to be equal
return m_data.m_index == rhs.m_data.m_index;
}
return m_data.m_hkey == rhs.m_data.m_hkey && m_data.m_index == rhs.m_data.m_index;
}
bool operator!=(const iterator_nothrow_t& rhs) const WI_NOEXCEPT
{
return !(*this == rhs);
}
iterator_nothrow_t& operator++() WI_NOEXCEPT
{
move_next();
return *this;
}
const iterator_nothrow_t& operator++() const WI_NOEXCEPT
{
move_next();
return *this;
}
private:
// container based on the class template type
T m_data{};
HRESULT m_last_error{};
};
} // namespace reg
} // namespace wil
#endif // __WIL_REGISTRY_HELPERS_INCLUDED