Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
36 changes: 36 additions & 0 deletions include/wil/registry.h
Original file line number Diff line number Diff line change
Expand Up @@ -970,6 +970,42 @@ namespace reg
return ::wil::reg::set_value_expanded_string_nothrow(key, nullptr, value_name, data);
}

/**
* @brief Writes a REG_MULTI_SZ value from an array of null-terminated strings
* @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 An array of `count` null-terminated strings to write to the specified registry value.
* Each string is marshaled into a contiguous null-terminator-delimited multi-sz string.
* @param count The number of strings in `data`
* @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, _In_reads_(count) const PCWSTR* data, size_t count) WI_NOEXCEPT
{
::wil::unique_process_heap_ptr<wchar_t> buffer;
DWORD bufferSizeBytes = 0;
RETURN_IF_FAILED(reg_view_details::get_multistring_from_strings_nothrow(data, count, buffer, &bufferSizeBytes));
return HRESULT_FROM_WIN32(::RegSetKeyValueW(key, subkey, value_name, REG_MULTI_SZ, buffer.get(), bufferSizeBytes));
}

/**
* @brief Writes a REG_MULTI_SZ value from an array of null-terminated strings
* @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 An array of `count` null-terminated strings to write to the specified registry value.
* Each string is marshaled into a contiguous null-terminator-delimited multi-sz string.
* @param count The number of strings in `data`
* @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, _In_reads_(count) const PCWSTR* data, size_t count) WI_NOEXCEPT
{
return ::wil::reg::set_value_multistring_nothrow(key, nullptr, value_name, data, count);
}

#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
64 changes: 64 additions & 0 deletions include/wil/registry_helpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,70 @@ namespace reg
}
#endif

/**
* @brief A nothrow translation function marshaling an array of null-terminated strings into a contiguous
* null-terminator-delimited REG_MULTI_SZ buffer allocated on the process heap.
* @param data An array of `count` null-terminated strings to marshal into a multi-sz buffer
* @param count The number of strings in `data`
* @param buffer Receives ownership of the process-heap-allocated wchar_t buffer on success
* @param bufferSizeBytes Receives the size, in bytes, of the marshaled buffer (including terminators)
* @return HRESULT error code indicating success or failure (does not throw C++ exceptions)
*/
inline HRESULT get_multistring_from_strings_nothrow(
_In_reads_(count) const PCWSTR* data, size_t count, ::wil::unique_process_heap_ptr<wchar_t>& buffer, _Out_ DWORD* bufferSizeBytes) WI_NOEXCEPT
{
buffer.reset();
*bufferSizeBytes = 0;

size_t total_size_chars = 1; // final terminator
for (size_t i = 0; i < count; ++i)
{
const size_t entry_chars = ::wcslen(data[i]) + 1;
if (total_size_chars + entry_chars < total_size_chars)
{
return E_INVALIDARG; // integer overflow
}
total_size_chars += entry_chars;
}

if (count == 0)
{
// An empty multi-string still requires a leading null plus the final terminator.
total_size_chars = 2;
}

if (total_size_chars > (MAXDWORD / sizeof(wchar_t)))
{
return E_INVALIDARG;
}
const size_t total_size_bytes = total_size_chars * sizeof(wchar_t);

::wil::unique_process_heap_ptr<wchar_t> result{static_cast<wchar_t*>(::HeapAlloc(::GetProcessHeap(), 0, total_size_bytes))};
RETURN_IF_NULL_ALLOC(result.get());

size_t offset = 0;
for (size_t i = 0; i < count; ++i)
{
// Each string is null-terminated and it is valid to read that null character, so copy length + 1
// characters to include the terminator in a single memcpy.
const size_t entry_chars = ::wcslen(data[i]) + 1;
memcpy(result.get() + offset, data[i], entry_chars * sizeof(wchar_t));
offset += entry_chars;
}

if (count == 0)
{
result.get()[offset++] = L'\0'; // leading null for an empty multi-string
}

result.get()[offset++] = L'\0'; // final terminator
WI_ASSERT(offset == total_size_chars);

buffer = wistd::move(result);
*bufferSizeBytes = static_cast<DWORD>(total_size_bytes);
return S_OK;
}

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

#if WIL_USE_STL && defined(WIL_ENABLE_EXCEPTIONS)
const auto to_ptrs = [](const std::vector<std::wstring>& strings) {
std::vector<PCWSTR> ptrs;
ptrs.reserve(strings.size());
for (const auto& s : strings)
{
ptrs.push_back(s.c_str());
}
return ptrs;
};

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)
{
const auto ptrs = to_ptrs(value);
REQUIRE_SUCCEEDED(wil::reg::set_value_multistring_nothrow(hkey.get(), stringValueName, ptrs.data(), ptrs.size()));
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"};
const auto testPtrs = to_ptrs(testValue);
REQUIRE_SUCCEEDED(wil::reg::set_value_multistring_nothrow(hkey.get(), nullptr, testPtrs.data(), testPtrs.size()));
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)
{
const auto ptrs = to_ptrs(value);
REQUIRE_SUCCEEDED(
wil::reg::set_value_multistring_nothrow(HKEY_CURRENT_USER, testSubkey, stringValueName, ptrs.data(), ptrs.size()));
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"};
const auto testPtrs = to_ptrs(testValue);
REQUIRE_SUCCEEDED(
wil::reg::set_value_multistring_nothrow(HKEY_CURRENT_USER, testSubkey, nullptr, testPtrs.data(), testPtrs.size()));
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, nullptr, 0));
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, nullptr, 0));
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, nullptr, 0));
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, nullptr, 0));
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 PCWSTR testValue[]{L"test"};
auto hr = wil::reg::set_value_multistring_nothrow(readOnlyKey.get(), stringValueName, testValue, ARRAYSIZE(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 PCWSTR testValue[]{L"alpha", L"bravo", L"charlie"};
REQUIRE_SUCCEEDED(wil::reg::set_value_multistring_nothrow(hkey.get(), stringValueName, testValue, ARRAYSIZE(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