Skip to content
Draft
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions include/wil/registry.h
Original file line number Diff line number Diff line change
Expand Up @@ -884,6 +884,44 @@ namespace reg
return ::wil::reg::set_value_expanded_string_nothrow(key, nullptr, value_name, data);
}

#if WIL_USE_STL || defined(WIL_DOXYGEN)
/**
* @brief Writes a REG_MULTI_SZ value from a std::vector<std::wstring>
* @param key An open or well-known registry key
* @param subkey The name of the subkey to append to `key`.
* If `nullptr`, then `key` is used without modification.
* @param value_name The name of the registry value whose data is to be updated.
* Can be nullptr to write to the unnamed default registry value.
* @param data A std::vector<std::wstring> to write to the specified registry value.
* Each string will be marshaled to a contiguous null-terminator-delimited multi-sz string
* @return HRESULT error code indicating success or failure (does not throw C++ exceptions)
*/
inline HRESULT set_value_multistring_nothrow(
HKEY key, _In_opt_ PCWSTR subkey, _In_opt_ PCWSTR value_name, const ::std::vector<::std::wstring>& data) WI_NOEXCEPT
{
wchar_t* buffer = nullptr;
DWORD bufferSizeBytes = 0;
RETURN_IF_FAILED(reg_view_details::get_multistring_from_wstrings_nothrow(data, &buffer, &bufferSizeBytes));
const auto hr = HRESULT_FROM_WIN32(::RegSetKeyValueW(key, subkey, value_name, REG_MULTI_SZ, buffer, bufferSizeBytes));
::HeapFree(::GetProcessHeap(), 0, buffer);
Comment thread
benhillis marked this conversation as resolved.
Outdated
return hr;
}

/**
* @brief Writes a REG_MULTI_SZ value from a std::vector<std::wstring>
* @param key An open or well-known registry key
* @param value_name The name of the registry value whose data is to be updated.
* Can be nullptr to write to the unnamed default registry value.
* @param data A std::vector<std::wstring> to write to the specified registry value.
* Each string will be marshaled to a contiguous null-terminator-delimited multi-sz string.
* @return HRESULT error code indicating success or failure (does not throw C++ exceptions)
*/
inline HRESULT set_value_multistring_nothrow(HKEY key, _In_opt_ PCWSTR value_name, const ::std::vector<::std::wstring>& data) WI_NOEXCEPT
{
return ::wil::reg::set_value_multistring_nothrow(key, nullptr, value_name, data);
}
#endif // WIL_USE_STL

#if defined(__WIL_OBJBASE_H_) || defined(WIL_DOXYGEN)
/**
* @brief Writes raw bytes into a registry value under a specified key of the specified type
Expand Down
51 changes: 51 additions & 0 deletions include/wil/registry_helpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,57 @@ namespace reg
}
#endif

#if WIL_USE_STL
inline HRESULT get_multistring_from_wstrings_nothrow(
const ::std::vector<::std::wstring>& data, _Out_ wchar_t** buffer, _Out_ DWORD* bufferSizeBytes) WI_NOEXCEPT
{
*buffer = nullptr;
*bufferSizeBytes = 0;

size_t total_size_bytes = sizeof(wchar_t); // final double-null terminator
for (const auto& str : data)
{
const size_t entry_size = (str.size() + 1) * sizeof(wchar_t);
if (total_size_bytes + entry_size < total_size_bytes || total_size_bytes + entry_size > MAXDWORD)
{
return E_INVALIDARG;
}
total_size_bytes += entry_size;
}

if (data.empty())
{
total_size_bytes = 2 * sizeof(wchar_t);
}

auto result = static_cast<wchar_t*>(::HeapAlloc(::GetProcessHeap(), 0, total_size_bytes));
if (!result)
{
return E_OUTOFMEMORY;
}

size_t offset = 0;
if (data.empty())
{
result[offset++] = L'\0';
}
else
{
for (const auto& str : data)
{
memcpy(result + offset, str.c_str(), str.size() * sizeof(wchar_t));
offset += str.size();
result[offset++] = L'\0';
Comment thread
benhillis marked this conversation as resolved.
Outdated
}
}
result[offset] = L'\0';

Comment thread
benhillis marked this conversation as resolved.
*buffer = result;
*bufferSizeBytes = static_cast<DWORD>(total_size_bytes);
return S_OK;
}
#endif

#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
Expand Down
105 changes: 105 additions & 0 deletions tests/RegistryTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3028,6 +3028,111 @@ TEST_CASE("BasicRegistryTests::multi-strings", "[registry]")
}
#endif
#endif

#if WIL_USE_STL && defined(WIL_ENABLE_EXCEPTIONS)
SECTION("set_value_multistring_nothrow/get_value_multistring_nothrow: with open key")
{
wil::unique_hkey hkey;
REQUIRE_SUCCEEDED(wil::reg::create_unique_key_nothrow(HKEY_CURRENT_USER, testSubkey, hkey, wil::reg::key_access::readwrite));

for (const auto& value : multiStringTestVector)
{
REQUIRE_SUCCEEDED(wil::reg::set_value_multistring_nothrow(hkey.get(), stringValueName, value));
auto result = wil::reg::get_value_multistring(hkey.get(), stringValueName);
// set_value_multistring_nothrow should produce the same result as set_value_multistring
wil::reg::set_value_multistring(hkey.get(), multiStringValueName, value);
auto expected = wil::reg::get_value_multistring(hkey.get(), multiStringValueName);
REQUIRE(result == expected);
}

// and verify default value name
const std::vector<std::wstring> testValue{L"hello", L"world"};
REQUIRE_SUCCEEDED(wil::reg::set_value_multistring_nothrow(hkey.get(), nullptr, testValue));
auto result = wil::reg::get_value_multistring(hkey.get(), nullptr);
REQUIRE(result == testValue);
}

SECTION("set_value_multistring_nothrow/get_value_multistring_nothrow: with string key")
{
for (const auto& value : multiStringTestVector)
{
REQUIRE_SUCCEEDED(wil::reg::set_value_multistring_nothrow(HKEY_CURRENT_USER, testSubkey, stringValueName, value));
auto result = wil::reg::get_value_multistring(HKEY_CURRENT_USER, testSubkey, stringValueName);
// set_value_multistring_nothrow should produce the same result as set_value_multistring
wil::reg::set_value_multistring(HKEY_CURRENT_USER, testSubkey, multiStringValueName, value);
auto expected = wil::reg::get_value_multistring(HKEY_CURRENT_USER, testSubkey, multiStringValueName);
REQUIRE(result == expected);
}

// and verify default value name
const std::vector<std::wstring> testValue{L"hello", L"world"};
REQUIRE_SUCCEEDED(wil::reg::set_value_multistring_nothrow(HKEY_CURRENT_USER, testSubkey, nullptr, testValue));
auto result = wil::reg::get_value_multistring(HKEY_CURRENT_USER, testSubkey, nullptr);
REQUIRE(result == testValue);
}

SECTION("set_value_multistring_nothrow: empty array with open key")
{
wil::unique_hkey hkey;
REQUIRE_SUCCEEDED(wil::reg::create_unique_key_nothrow(HKEY_CURRENT_USER, testSubkey, hkey, wil::reg::key_access::readwrite));

// When passed an empty array, set_value_multistring_nothrow writes 2 null-terminators
// (i.e. a single empty string), matching the behavior of set_value_multistring
const std::vector<std::wstring> arrayOfOne{L""};
REQUIRE_SUCCEEDED(wil::reg::set_value_multistring_nothrow(hkey.get(), stringValueName, test_multistring_empty));
auto result = wil::reg::get_value_multistring(hkey.get(), stringValueName);
REQUIRE(result == arrayOfOne);

// and verify default value name
REQUIRE_SUCCEEDED(wil::reg::set_value_multistring_nothrow(hkey.get(), nullptr, test_multistring_empty));
result = wil::reg::get_value_multistring(hkey.get(), nullptr);
REQUIRE(result == arrayOfOne);
}

SECTION("set_value_multistring_nothrow: empty array with string key")
{
const std::vector<std::wstring> arrayOfOne{L""};
REQUIRE_SUCCEEDED(wil::reg::set_value_multistring_nothrow(HKEY_CURRENT_USER, testSubkey, stringValueName, test_multistring_empty));
auto result = wil::reg::get_value_multistring(HKEY_CURRENT_USER, testSubkey, stringValueName);
REQUIRE(result == arrayOfOne);

// and verify default value name
REQUIRE_SUCCEEDED(wil::reg::set_value_multistring_nothrow(HKEY_CURRENT_USER, testSubkey, nullptr, test_multistring_empty));
result = wil::reg::get_value_multistring(HKEY_CURRENT_USER, testSubkey, nullptr);
REQUIRE(result == arrayOfOne);
}

SECTION("set_value_multistring_nothrow: fails with E_ACCESSDENIED on read-only key")
{
wil::unique_hkey hkey;
REQUIRE_SUCCEEDED(wil::reg::create_unique_key_nothrow(HKEY_CURRENT_USER, testSubkey, hkey, wil::reg::key_access::readwrite));

wil::unique_hkey readOnlyKey;
REQUIRE_SUCCEEDED(wil::reg::open_unique_key_nothrow(HKEY_CURRENT_USER, testSubkey, readOnlyKey, wil::reg::key_access::read));

const std::vector<std::wstring> testValue{L"test"};
auto hr = wil::reg::set_value_multistring_nothrow(readOnlyKey.get(), stringValueName, testValue);
REQUIRE(hr == E_ACCESSDENIED);
}

SECTION("set_value_multistring_nothrow: round-trip with nothrow get via cotaskmem")
{
wil::unique_hkey hkey;
REQUIRE_SUCCEEDED(wil::reg::create_unique_key_nothrow(HKEY_CURRENT_USER, testSubkey, hkey, wil::reg::key_access::readwrite));

const std::vector<std::wstring> testValue{L"alpha", L"bravo", L"charlie"};
REQUIRE_SUCCEEDED(wil::reg::set_value_multistring_nothrow(hkey.get(), stringValueName, testValue));

#if defined(__WIL_OBJBASE_H_)
wil::unique_cotaskmem_array_ptr<wil::unique_cotaskmem_string> result{};
REQUIRE_SUCCEEDED(wil::reg::get_value_multistring_nothrow(hkey.get(), stringValueName, result));
REQUIRE(result.size() == 3);
REQUIRE(std::wstring_view(result[0]) == L"alpha");
REQUIRE(std::wstring_view(result[1]) == L"bravo");
REQUIRE(std::wstring_view(result[2]) == L"charlie");
#endif // defined(__WIL_OBJBASE_H_)
}
#endif // WIL_USE_STL && defined(WIL_ENABLE_EXCEPTIONS)
}

#if defined(__WIL_OBJBASE_H_)
Expand Down
Loading