diff --git a/include/wil/registry.h b/include/wil/registry.h index 5aa89d01..7cc591e6 100644 --- a/include/wil/registry.h +++ b/include/wil/registry.h @@ -884,6 +884,42 @@ 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 + * @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 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 + { + ::wil::unique_process_heap_ptr buffer; + DWORD bufferSizeBytes = 0; + RETURN_IF_FAILED(reg_view_details::get_multistring_from_wstrings_nothrow(data, 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 a std::vector + * @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 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 diff --git a/include/wil/registry_helpers.h b/include/wil/registry_helpers.h index f796864f..d5be7aad 100644 --- a/include/wil/registry_helpers.h +++ b/include/wil/registry_helpers.h @@ -237,6 +237,67 @@ namespace reg } #endif +#if WIL_USE_STL + /** + * @brief A nothrow translation function marshaling a std::vector into a contiguous + * null-terminator-delimited REG_MULTI_SZ buffer allocated on the process heap. + * @param data The std::vector to marshal into a multi-sz buffer + * @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_wstrings_nothrow( + const ::std::vector<::std::wstring>& data, ::wil::unique_process_heap_ptr& buffer, _Out_ DWORD* bufferSizeBytes) WI_NOEXCEPT + { + buffer.reset(); + *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()) + { + // An empty multi-string still requires a leading null plus the final terminator. + total_size_bytes = 2 * sizeof(wchar_t); + } + + ::wil::unique_process_heap_ptr result{static_cast(::HeapAlloc(::GetProcessHeap(), 0, total_size_bytes))}; + if (!result) + { + return E_OUTOFMEMORY; + } + + size_t offset = 0; + for (const auto& str : data) + { + // c_str() is guaranteed to be null-terminated and it is valid to read that null character, + // so copy size() + 1 characters to include the terminator in a single memcpy. + memcpy(result.get() + offset, str.c_str(), (str.size() + 1) * sizeof(wchar_t)); + offset += str.size() + 1; + } + + if (data.empty()) + { + result.get()[offset++] = L'\0'; // leading null for an empty multi-string + } + + result.get()[offset++] = L'\0'; // final double-null terminator + WI_ASSERT(offset == (total_size_bytes / sizeof(wchar_t))); + + buffer = wistd::move(result); + *bufferSizeBytes = static_cast(total_size_bytes); + return S_OK; + } +#endif + #if defined(__WIL_OBJBASE_H_) template void get_multistring_bytearray_from_strings_nothrow(const PCWSTR data[C], ::wil::unique_cotaskmem_array_ptr& multistring) WI_NOEXCEPT diff --git a/tests/RegistryTests.cpp b/tests/RegistryTests.cpp index f774e95c..7bfa6594 100644 --- a/tests/RegistryTests.cpp +++ b/tests/RegistryTests.cpp @@ -3104,6 +3104,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 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 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 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 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 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 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 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_)