Skip to content

Commit ff19b42

Browse files
Ben HillisCopilot
andcommitted
Add set_value_multistring_nothrow for REG_MULTI_SZ writes
Adds set_value_multistring_nothrow(), the missing nothrow counterpart to set_value_multistring(). Takes an array of null-terminated strings as (PCWSTR* data, size_t count) so it works in both exception-free and no-STL builds. Marshaling is done by an exceptions-free helper that allocates via unique_process_heap_ptr and builds the null-delimited, double-null terminated REG_MULTI_SZ buffer directly, with no std::vector or try/CATCH_RETURN(). Two overloads matching the existing pattern (with and without a subkey). Fixes #479 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent cb3c041 commit ff19b42

3 files changed

Lines changed: 221 additions & 0 deletions

File tree

include/wil/registry.h

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -884,6 +884,42 @@ namespace reg
884884
return ::wil::reg::set_value_expanded_string_nothrow(key, nullptr, value_name, data);
885885
}
886886

887+
/**
888+
* @brief Writes a REG_MULTI_SZ value from an array of null-terminated strings
889+
* @param key An open or well-known registry key
890+
* @param subkey The name of the subkey to append to `key`.
891+
* If `nullptr`, then `key` is used without modification.
892+
* @param value_name The name of the registry value whose data is to be updated.
893+
* Can be nullptr to write to the unnamed default registry value.
894+
* @param data An array of `count` null-terminated strings to write to the specified registry value.
895+
* Each string is marshaled into a contiguous null-terminator-delimited multi-sz string.
896+
* @param count The number of strings in `data`
897+
* @return HRESULT error code indicating success or failure (does not throw C++ exceptions)
898+
*/
899+
inline HRESULT set_value_multistring_nothrow(
900+
HKEY key, _In_opt_ PCWSTR subkey, _In_opt_ PCWSTR value_name, _In_reads_(count) const PCWSTR* data, size_t count) WI_NOEXCEPT
901+
{
902+
::wil::unique_process_heap_ptr<wchar_t> buffer;
903+
DWORD bufferSizeBytes = 0;
904+
RETURN_IF_FAILED(reg_view_details::get_multistring_from_strings_nothrow(data, count, buffer, &bufferSizeBytes));
905+
return HRESULT_FROM_WIN32(::RegSetKeyValueW(key, subkey, value_name, REG_MULTI_SZ, buffer.get(), bufferSizeBytes));
906+
}
907+
908+
/**
909+
* @brief Writes a REG_MULTI_SZ value from an array of null-terminated strings
910+
* @param key An open or well-known registry key
911+
* @param value_name The name of the registry value whose data is to be updated.
912+
* Can be nullptr to write to the unnamed default registry value.
913+
* @param data An array of `count` null-terminated strings to write to the specified registry value.
914+
* Each string is marshaled into a contiguous null-terminator-delimited multi-sz string.
915+
* @param count The number of strings in `data`
916+
* @return HRESULT error code indicating success or failure (does not throw C++ exceptions)
917+
*/
918+
inline HRESULT set_value_multistring_nothrow(HKEY key, _In_opt_ PCWSTR value_name, _In_reads_(count) const PCWSTR* data, size_t count) WI_NOEXCEPT
919+
{
920+
return ::wil::reg::set_value_multistring_nothrow(key, nullptr, value_name, data, count);
921+
}
922+
887923
#if defined(__WIL_OBJBASE_H_) || defined(WIL_DOXYGEN)
888924
/**
889925
* @brief Writes raw bytes into a registry value under a specified key of the specified type

include/wil/registry_helpers.h

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,70 @@ namespace reg
237237
}
238238
#endif
239239

240+
/**
241+
* @brief A nothrow translation function marshaling an array of null-terminated strings into a contiguous
242+
* null-terminator-delimited REG_MULTI_SZ buffer allocated on the process heap.
243+
* @param data An array of `count` null-terminated strings to marshal into a multi-sz buffer
244+
* @param count The number of strings in `data`
245+
* @param buffer Receives ownership of the process-heap-allocated wchar_t buffer on success
246+
* @param bufferSizeBytes Receives the size, in bytes, of the marshaled buffer (including terminators)
247+
* @return HRESULT error code indicating success or failure (does not throw C++ exceptions)
248+
*/
249+
inline HRESULT get_multistring_from_strings_nothrow(
250+
_In_reads_(count) const PCWSTR* data, size_t count, ::wil::unique_process_heap_ptr<wchar_t>& buffer, _Out_ DWORD* bufferSizeBytes) WI_NOEXCEPT
251+
{
252+
buffer.reset();
253+
*bufferSizeBytes = 0;
254+
255+
size_t total_size_chars = 1; // final terminator
256+
for (size_t i = 0; i < count; ++i)
257+
{
258+
const size_t entry_chars = ::wcslen(data[i]) + 1;
259+
if (total_size_chars + entry_chars < total_size_chars)
260+
{
261+
return E_INVALIDARG; // integer overflow
262+
}
263+
total_size_chars += entry_chars;
264+
}
265+
266+
if (count == 0)
267+
{
268+
// An empty multi-string still requires a leading null plus the final terminator.
269+
total_size_chars = 2;
270+
}
271+
272+
if (total_size_chars > (MAXDWORD / sizeof(wchar_t)))
273+
{
274+
return E_INVALIDARG;
275+
}
276+
const size_t total_size_bytes = total_size_chars * sizeof(wchar_t);
277+
278+
::wil::unique_process_heap_ptr<wchar_t> result{static_cast<wchar_t*>(::HeapAlloc(::GetProcessHeap(), 0, total_size_bytes))};
279+
RETURN_IF_NULL_ALLOC(result.get());
280+
281+
size_t offset = 0;
282+
for (size_t i = 0; i < count; ++i)
283+
{
284+
// Each string is null-terminated and it is valid to read that null character, so copy length + 1
285+
// characters to include the terminator in a single memcpy.
286+
const size_t entry_chars = ::wcslen(data[i]) + 1;
287+
memcpy(result.get() + offset, data[i], entry_chars * sizeof(wchar_t));
288+
offset += entry_chars;
289+
}
290+
291+
if (count == 0)
292+
{
293+
result.get()[offset++] = L'\0'; // leading null for an empty multi-string
294+
}
295+
296+
result.get()[offset++] = L'\0'; // final terminator
297+
WI_ASSERT(offset == total_size_chars);
298+
299+
buffer = wistd::move(result);
300+
*bufferSizeBytes = static_cast<DWORD>(total_size_bytes);
301+
return S_OK;
302+
}
303+
240304
#if defined(__WIL_OBJBASE_H_)
241305
template <size_t C>
242306
void get_multistring_bytearray_from_strings_nothrow(const PCWSTR data[C], ::wil::unique_cotaskmem_array_ptr<BYTE>& multistring) WI_NOEXCEPT

tests/RegistryTests.cpp

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3104,6 +3104,127 @@ TEST_CASE("BasicRegistryTests::multi-strings", "[registry]")
31043104
}
31053105
#endif
31063106
#endif
3107+
3108+
#if WIL_USE_STL && defined(WIL_ENABLE_EXCEPTIONS)
3109+
const auto to_ptrs = [](const std::vector<std::wstring>& strings) {
3110+
std::vector<PCWSTR> ptrs;
3111+
ptrs.reserve(strings.size());
3112+
for (const auto& s : strings)
3113+
{
3114+
ptrs.push_back(s.c_str());
3115+
}
3116+
return ptrs;
3117+
};
3118+
3119+
SECTION("set_value_multistring_nothrow/get_value_multistring_nothrow: with open key")
3120+
{
3121+
wil::unique_hkey hkey;
3122+
REQUIRE_SUCCEEDED(wil::reg::create_unique_key_nothrow(HKEY_CURRENT_USER, testSubkey, hkey, wil::reg::key_access::readwrite));
3123+
3124+
for (const auto& value : multiStringTestVector)
3125+
{
3126+
const auto ptrs = to_ptrs(value);
3127+
REQUIRE_SUCCEEDED(wil::reg::set_value_multistring_nothrow(hkey.get(), stringValueName, ptrs.data(), ptrs.size()));
3128+
auto result = wil::reg::get_value_multistring(hkey.get(), stringValueName);
3129+
// set_value_multistring_nothrow should produce the same result as set_value_multistring
3130+
wil::reg::set_value_multistring(hkey.get(), multiStringValueName, value);
3131+
auto expected = wil::reg::get_value_multistring(hkey.get(), multiStringValueName);
3132+
REQUIRE(result == expected);
3133+
}
3134+
3135+
// and verify default value name
3136+
const std::vector<std::wstring> testValue{L"hello", L"world"};
3137+
const auto testPtrs = to_ptrs(testValue);
3138+
REQUIRE_SUCCEEDED(wil::reg::set_value_multistring_nothrow(hkey.get(), nullptr, testPtrs.data(), testPtrs.size()));
3139+
auto result = wil::reg::get_value_multistring(hkey.get(), nullptr);
3140+
REQUIRE(result == testValue);
3141+
}
3142+
3143+
SECTION("set_value_multistring_nothrow/get_value_multistring_nothrow: with string key")
3144+
{
3145+
for (const auto& value : multiStringTestVector)
3146+
{
3147+
const auto ptrs = to_ptrs(value);
3148+
REQUIRE_SUCCEEDED(
3149+
wil::reg::set_value_multistring_nothrow(HKEY_CURRENT_USER, testSubkey, stringValueName, ptrs.data(), ptrs.size()));
3150+
auto result = wil::reg::get_value_multistring(HKEY_CURRENT_USER, testSubkey, stringValueName);
3151+
// set_value_multistring_nothrow should produce the same result as set_value_multistring
3152+
wil::reg::set_value_multistring(HKEY_CURRENT_USER, testSubkey, multiStringValueName, value);
3153+
auto expected = wil::reg::get_value_multistring(HKEY_CURRENT_USER, testSubkey, multiStringValueName);
3154+
REQUIRE(result == expected);
3155+
}
3156+
3157+
// and verify default value name
3158+
const std::vector<std::wstring> testValue{L"hello", L"world"};
3159+
const auto testPtrs = to_ptrs(testValue);
3160+
REQUIRE_SUCCEEDED(
3161+
wil::reg::set_value_multistring_nothrow(HKEY_CURRENT_USER, testSubkey, nullptr, testPtrs.data(), testPtrs.size()));
3162+
auto result = wil::reg::get_value_multistring(HKEY_CURRENT_USER, testSubkey, nullptr);
3163+
REQUIRE(result == testValue);
3164+
}
3165+
3166+
SECTION("set_value_multistring_nothrow: empty array with open key")
3167+
{
3168+
wil::unique_hkey hkey;
3169+
REQUIRE_SUCCEEDED(wil::reg::create_unique_key_nothrow(HKEY_CURRENT_USER, testSubkey, hkey, wil::reg::key_access::readwrite));
3170+
3171+
// When passed an empty array, set_value_multistring_nothrow writes 2 null-terminators
3172+
// (i.e. a single empty string), matching the behavior of set_value_multistring
3173+
const std::vector<std::wstring> arrayOfOne{L""};
3174+
REQUIRE_SUCCEEDED(wil::reg::set_value_multistring_nothrow(hkey.get(), stringValueName, nullptr, 0));
3175+
auto result = wil::reg::get_value_multistring(hkey.get(), stringValueName);
3176+
REQUIRE(result == arrayOfOne);
3177+
3178+
// and verify default value name
3179+
REQUIRE_SUCCEEDED(wil::reg::set_value_multistring_nothrow(hkey.get(), nullptr, nullptr, 0));
3180+
result = wil::reg::get_value_multistring(hkey.get(), nullptr);
3181+
REQUIRE(result == arrayOfOne);
3182+
}
3183+
3184+
SECTION("set_value_multistring_nothrow: empty array with string key")
3185+
{
3186+
const std::vector<std::wstring> arrayOfOne{L""};
3187+
REQUIRE_SUCCEEDED(wil::reg::set_value_multistring_nothrow(HKEY_CURRENT_USER, testSubkey, stringValueName, nullptr, 0));
3188+
auto result = wil::reg::get_value_multistring(HKEY_CURRENT_USER, testSubkey, stringValueName);
3189+
REQUIRE(result == arrayOfOne);
3190+
3191+
// and verify default value name
3192+
REQUIRE_SUCCEEDED(wil::reg::set_value_multistring_nothrow(HKEY_CURRENT_USER, testSubkey, nullptr, nullptr, 0));
3193+
result = wil::reg::get_value_multistring(HKEY_CURRENT_USER, testSubkey, nullptr);
3194+
REQUIRE(result == arrayOfOne);
3195+
}
3196+
3197+
SECTION("set_value_multistring_nothrow: fails with E_ACCESSDENIED on read-only key")
3198+
{
3199+
wil::unique_hkey hkey;
3200+
REQUIRE_SUCCEEDED(wil::reg::create_unique_key_nothrow(HKEY_CURRENT_USER, testSubkey, hkey, wil::reg::key_access::readwrite));
3201+
3202+
wil::unique_hkey readOnlyKey;
3203+
REQUIRE_SUCCEEDED(wil::reg::open_unique_key_nothrow(HKEY_CURRENT_USER, testSubkey, readOnlyKey, wil::reg::key_access::read));
3204+
3205+
const PCWSTR testValue[]{L"test"};
3206+
auto hr = wil::reg::set_value_multistring_nothrow(readOnlyKey.get(), stringValueName, testValue, ARRAYSIZE(testValue));
3207+
REQUIRE(hr == E_ACCESSDENIED);
3208+
}
3209+
3210+
SECTION("set_value_multistring_nothrow: round-trip with nothrow get via cotaskmem")
3211+
{
3212+
wil::unique_hkey hkey;
3213+
REQUIRE_SUCCEEDED(wil::reg::create_unique_key_nothrow(HKEY_CURRENT_USER, testSubkey, hkey, wil::reg::key_access::readwrite));
3214+
3215+
const PCWSTR testValue[]{L"alpha", L"bravo", L"charlie"};
3216+
REQUIRE_SUCCEEDED(wil::reg::set_value_multistring_nothrow(hkey.get(), stringValueName, testValue, ARRAYSIZE(testValue)));
3217+
3218+
#if defined(__WIL_OBJBASE_H_)
3219+
wil::unique_cotaskmem_array_ptr<wil::unique_cotaskmem_string> result{};
3220+
REQUIRE_SUCCEEDED(wil::reg::get_value_multistring_nothrow(hkey.get(), stringValueName, result));
3221+
REQUIRE(result.size() == 3);
3222+
REQUIRE(std::wstring_view(result[0]) == L"alpha");
3223+
REQUIRE(std::wstring_view(result[1]) == L"bravo");
3224+
REQUIRE(std::wstring_view(result[2]) == L"charlie");
3225+
#endif // defined(__WIL_OBJBASE_H_)
3226+
}
3227+
#endif // WIL_USE_STL && defined(WIL_ENABLE_EXCEPTIONS)
31073228
}
31083229

31093230
#if defined(__WIL_OBJBASE_H_)

0 commit comments

Comments
 (0)