May 2017

Volume 32 Number 5

[C++]

Use Modern C++ to Access the Windows Registry

By Giovanni Dicanio

The Windows OS exposes a series of C-interface APIs to give developers access to the registry. Some of these APIs are fairly low level and require programmers to pay attention to many details. Starting with Windows Vista, a kind of higher-level API was added to the mix: the RegGetValue function (bit.ly/2jXtfpJ). Before this API was introduced, to read a value from the registry, you first had to open the desired registry key containing the value calling RegOpenKeyEx. Then, you had to call the RegQueryValueEx API, dealing with many complex details. For example, if you read a string value with RegQueryValueEx, the returned string is not guaranteed to be properly NUL-terminated, which can cause a series of dangerous security bugs in your code. To prevent that from happening, you have to pay attention to check if there’s a NUL terminator in the returned string; if there isn’t any, you have to add it. Moreover, you have to make sure you properly close the open key, calling RegCloseKey.

Of course, opening the registry key could fail, so you have to add code to handle that, as well. The RegGetValue API simplifies this workflow, as it automatically opens the desired registry key, closes it after use, and it properly NUL-terminates strings before returning them to the caller. Despite this simplification, the RegGetValue function is still a low-level C-interface function; moreover, the fact that it can handle several different types of registry values (from DWORDs to strings to binary data), makes its interface complicated for you to program.

Thankfully, you can use modern C++ to properly build higher-level abstractions around this RegGetValue Win32 API, offering a simplified interface to read values of different types from the registry.

Representing Errors Using Exceptions

The RegGetValue API is a C-interface API and as such it signals error conditions to the caller using return codes. In particular, this function returns a value of type LONG: ERROR_SUCCESS (that is, zero) in the case of success and a different value in the case of errors. For example, if the output buffer provided by the caller isn’t large enough for the API to write its data, the function returns ERROR_MORE_DATA. For building a higher-level C++ interface around this C API, you can define a C++ exception class to represent errors. This class can be derived from the standard std::runtime_error class and you can embed the LONG error code returned by RegGetValue inside it:

class RegistryError
  : public std::runtime_error
{
public:
  ...
private:
  LONG m_errorCode;
};

In addition, other information pieces can be embedded in the exception object; for example, the HKEY and the name of the sub key. This is just a basic implementation.

You can add a constructor to create an instance of this exception class using an error message and the return code from the failed RegGetValue call:

RegistryError(const char* message, LONG errorCode)
  : std::runtime_error{message}
  , m_errorCode{errorCode}
{}

And the error code can be exposed to clients using a read-only accessor (getter):

LONG ErrorCode() const noexcept
{
  return m_errorCode;
}

Now that you’ve built this exception class, you can proceed to wrap the RegGetValue C API in a higher-level C++ interface that’s easier to use and less bug-prone.

Reading a DWORD Value from the Registry

Let’s start with a simple operation: using the RegGetValue API to read a DWORD value from the registry. The usage pattern in this case is pretty simple. But first, let’s see what kind of interface can be defined in C++ for managing this case.

Here’s the RegGetValue API prototype:

LONG WINAPI RegGetValue(
  _In_        HKEY    hkey,
  _In_opt_    LPCTSTR lpSubKey,
  _In_opt_    LPCTSTR lpValue,
  _In_opt_    DWORD   dwFlags,
  _Out_opt_   LPDWORD pdwType,
  _Out_opt_   PVOID   pvData,
  _Inout_opt_ LPDWORD pcbData
);

As you can see, this C-interface function takes highly generic data, like a void* output buffer (pvData) and an input/output buffer size parameter (pcbData). Moreover, there are C-style strings (lpSubKey and lpValue) that identify the registry key and the specific value name under that key. You can massage this C function prototype a bit, making it simpler for C++ callers.

First, as you’re going to signal error conditions throwing C++ exceptions, the DWORD value read from the registry can just be returned by the C++ wrapper as a return value. This automatically eliminates the need of the raw void* output buffer parameter (pvData) and the associated size parameter (pcbData).

Moreover, as you’re using C++, it’s better to represent Unicode (UTF-16) strings using the std::wstring class instead of C-style raw pointers. So, you can define this much simpler C++ function to read a DWORD value from the registry:

DWORD RegGetDword(
  HKEY hKey,
  const std::wstring& subKey,
  const std::wstring& value
)

As you can see, there are no PVOID and LPDWORD parameters; the input strings are passed via const references to std::wstring objects and the value read from the registry is returned as a DWORD by this C++ function. This is definitely a much simpler and higher-level interface.

Now let’s dive into the implementation. As mentioned, the invoke pattern for RegGetValue in this case is fairly simple. You just have to declare a DWORD variable that will store the value read from the registry:

DWORD data{};

Then you need another DWORD variable that represents the size (in bytes) of the output buffer written by RegGetValue. Note that the output buffer in this simple case is just the previous “data” variable, and its size is constantly the size of a DWORD:

DWORD dataSize = sizeof(data);

However, please note that you can’t mark dataSize as “const” because it’s both an input and an output parameter for RegGetValue.

Then you can invoke the RegGetValue API:

LONG retCode = ::RegGetValue(
  hKey,
  subKey.c_str(),
  value.c_str(),
  RRF_RT_REG_DWORD,
  nullptr,
  &data,
  &dataSize
);

The input wstring objects are converted to raw C-style string pointers using the wstring::c_str method. The RRF_RT_REG_DWORD flag restricts the type of the registry value to DWORD. If the registry value you’re attempting to read is of a different type, the RegGetValue function call safely fails.

The last two parameters represent the address of the output buffer (in this case, the address of the data variable) and the address of a variable that stores the size of the output buffer. In fact, on return, RegGetValue reports the size of the data written to the output buffer. In this case of reading a simple DWORD, the size of data is always 4 bytes, that is, sizeof(DWORD). However, this size parameter is more important for variable-size values such as strings; I’ll discuss that later in this article.

After invoking the RegGetValue function, you can check the return code and throw an exception in case of error:

if (retCode != ERROR_SUCCESS)
{
  throw RegistryError{"Cannot read DWORD from registry.", retCode};
}

Note that the error code (retCode) returned by RegGetValue is embedded in the exception object and can be later retrieved by the code that will process the exception.

Or, on success, the DWORD data variable can just be returned to the caller:

return data;

That’s it for the function implementation.

The caller can simply invoke this C++ wrapper function with code like this:

DWORD data = RegGetDword(HKEY_CURRENT_USER, subkey, L"MyDwordValue");

Note how simple this code is when compared to the original RegGetValue C API call. You just pass a handle to an open registry key (in this example, the HKEY_CURRENT_USER predefined key), a string containing the sub key, and the value name. On success, the DWORD value is returned to the caller. On the other hand, on error, a custom exception of type RegistryError is thrown. This kind of code is higher level and much simpler than invoking RegGetValue. In fact, the complexity of RegGetValue has been hidden inside this custom RegGetDword C++ wrapper function.

You can use the same pattern to read a QWORD (64-bit data) value from the registry; in this case, you just have to substitute the DWORD type for the registry value with the 64-bit ULONGLONG.

Reading a String Value from the Registry

Reading a DWORD value from the registry is fairly simple: just one call to the RegGetValue Win32 API is sufficient. That’s mainly because a DWORD value is of fixed size—four bytes—the size of a DWORD. On the other hand, reading strings from the registry introduces another layer of complexity because strings are variable-size data. The idea in this case is to call the RegGetValue API twice: In the first call, you request this API to return the desired size for the output string buffer. Next, you dynamically allocate a proper size buffer. Finally, you make a second call to RegGetValue to actually write the string data into the previously allocated buffer. (This pattern was discussed in detail in my previous article, “Using STL Strings at Win32 API Boundaries,” at msdn.com/magazine/mt238407).

First, take a look at the prototype of the C++ higher-level wrapper function:

std::wstring RegGetString(
  HKEY hKey,
  const std::wstring& subKey,
  const std::wstring& value
)

As in the DWORD case, this is much simplified with respect to the original complex RegGetValue C API prototype. The string value is returned from the function as a std::wstring instance. Instead, in case of errors, an exception is thrown. The sub-key name and the value name are passed as input wstring const reference parameters, as well.

Now I’ll discuss the implementation code.

As I wrote, the idea is to first call the RegGetValue API to get the size of the output buffer to store the string value:

DWORD dataSize{};
LONG retCode = ::RegGetValue(
  hKey,
  subKey.c_str(),
  value.c_str(),
  RRF_RT_REG_SZ,
  nullptr,
  nullptr,
  &dataSize
);

You can see a call syntax similar to the previous DWORD value case. The wstring objects are converted to C-style string pointers invoking the wstring::c_str method. The RRF_RT_REG_SZ flag in this case restricts the valid registry type to the string type (REG_SZ). On success, the RegGetValue API will write the desired output buffer size (expressed in bytes) in the dataSize variable.

On failure, you have to throw an exception of the custom RegistryError class:

if (retCode != ERROR_SUCCESS)
{
  throw RegistryError{"Cannot read string from registry", retCode};
}

Now that you know the desired output buffer size, you can allocate a wstring object of the required size for the output string:

std::wstring data;
data.resize(dataSize / sizeof(wchar_t));

Note that the dataSize value returned by RegGetValue is expressed in bytes, but the wstring::resize method expects a size expressed in wchar_t count. So, you have to scale from bytes to wchar_t, dividing the former byte size value by sizeof(wchar_t).

Now that you have a string with enough room allocated, you can pass a pointer to its internal buffer to the RegGetValue API, which this time will write the actual string’s data into the provided buffer:

retCode = ::RegGetValue(
  hKey,
  subKey.c_str(),
  value.c_str(),
  RRF_RT_REG_SZ,
  nullptr,
  &data[0],
  &dataSize
);

The &data[0] is the address of the wstring internal buffer that will be written by the RegGetValue API.

As usual, it’s important to verify the result of the API call and throw an exception in case of error:

if (retCode != ERROR_SUCCESS)
{
  throw RegistryError{"Cannot read string from registry", retCode};
}

Note that on success, RegGetValue writes the actual result string size (in bytes) in the dataSize variable. You must resize the wstring object according to this size. As dataSize is expressed in bytes, it’s better to convert it to the corresponding wchar_t count when dealing with wstrings:

DWORD stringLengthInWchars = dataSize / sizeof(wchar_t);

Moreover, dataSize includes the terminating NUL character for the output string. However, wstring objects are already NUL-­terminated, so you must pay attention to avoid a spurious and bogus double-NUL termination for the read string. You have to chop off the NUL-terminator written by RegGetValue:

stringLengthInWchars--; // Exclude the NUL written by the Win32 API
data.resize(stringLengthInWchars);

Note that the RegGetValue API guarantees a NUL-terminated string on success, even if the original string stored in the registry wasn’t NUL-terminated. This is a much safer behavior than the older RegQueryValueEx API, which didn’t guarantee NUL-termination for the returned strings. So, the caller had to write additional code to properly take that case into account, increasing the overall code complexity and bug surface area.

Now that the data variable contains the string value read from the registry, you can return it to the caller on function exit:

return data;

Once you have this convenient RegGetString C++ wrapper around the RegGetValue low-level C API, you can invoke it like this:

wstring s = RegGetString(HKEY_CURRENT_USER, subkey, L"MyStringValue");

As in the DWORD case, you’ve raised the level of abstraction from the RegGetValue Win32 API, providing an easy-to-use and hard-to-misuse C++ wrapper function to read a string value from the registry. All the details and complexity of dealing with the RegGetValue API are safely hidden inside the body of this custom RegGetString C++ function.

Reading Multi-String Values from the Registry

Another type of registry value is the so-called “multi-string”: Basically, this is a set of double-NUL-terminated strings packed in a single registry value. This double-NUL-terminated string data structure consists of a series of C-style NUL-terminated strings that occupy adjacent memory locations. The end of the sequence is marked by an additional NUL terminator, so the whole structure is terminated by two NULs. For more details on this data structure, see the blog post, “What Is the Format of a Double-Null-Terminated String with No Strings?” (bit.ly/2jCqg2u).

The usage pattern of the RegGetValue Win32 API in this case is very similar to the previous case of single strings. That is, first the RegGetValue API is invoked to get the size of the whole destination buffer containing the desired data (in this case, the whole sequence of adjacent strings terminated with a double-NUL). Then, a buffer of such size is dynamically allocated. And, finally, the RegGetValue function is called for the second time, passing the address of the previously allocated buffer so the API can write the actual multi-string data into that buffer.

In this case, you have to pay attention to the data structure storing the double-NUL-terminated string. In fact, while a std::wstring can properly contain embedded NULs, it could be potentially used to store a double-NUL-terminated string structure, but I prefer to raise the level of abstraction and parse the double-NUL-terminated string into a higher-level, more convenient vector<wstring>.

So, the prototype of your C++ wrapper function to read multi-string values from the registry can look like this:

std::vector<std::wstring> RegGetMultiString(
  HKEY hKey,
  const std::wstring& subKey,
  const std::wstring& value
)

On success, the multi-string will be returned to the caller as a nice vector<wstring>. On the other hand, on error, an exception in the usual RegistryError form will be thrown.

Inside the body of your C++ wrapper function, first you invoke the RegGetValue API to get the size of the desired output buffer to store the multi-string:

DWORD dataSize{};
LONG retCode = ::RegGetValue(
  hKey,
  subKey.c_str(),
  value.c_str(),
  RRF_RT_REG_MULTI_SZ,
  nullptr,
  nullptr,
  &dataSize
);

Note the use of the RRF_RT_REG_MULTI_SZ flag this time to specify the multi-string registry value type.

As usual, in case of error, an exception is thrown, embedding the error code in the RegistryError object:

if (retCode != ERROR_SUCCESS)
{
  throw RegistryError{"Cannot read multi-string from registry", retCode};
}

On success, you allocate a buffer of proper size, to store the whole multi-string:

std::vector<wchar_t> data;
data.resize(dataSize / sizeof(wchar_t));

I consider a vector<wchar_t> much clearer than a wstring to represent the multi-string raw buffer. Note also that the size value returned by the RegGetValue API is expressed in bytes, so it must be properly converted to a wchar_t count before passing it to the vector::resize method.

Then, the RegGetValue API can be invoked for the second time, to write the actual multi-string data into the buffer previously allocated:

retCode = ::RegGetValue(
  hKey,
  subKey.c_str(),
  value.c_str(),
  RRF_RT_REG_MULTI_SZ,
  nullptr,
  &data[0],
  &dataSize
);

The &data[0] argument points to the beginning of the output buffer.

Again, you have to check the API return code and signal errors throwing a C++ exception:

if (retCode != ERROR_SUCCESS)
{
  throw RegistryError{"Cannot read multi-string"
    from registry", retCode};
}

It’s also good to properly resize the data buffer with the dataSize value returned as an output parameter by the RegGetValue API:

data.resize( dataSize / sizeof(wchar_t) );

At this point, the data variable (which is a vector<wchar_t>) stores the double-NUL-terminated string sequence. The last step is to parse this data structure and convert it into a higher-level, more convenient vector<wstring>:

// Parse the double-NUL-terminated string into a vector<wstring>
std::vector<std::wstring> result;
const wchar_t* currStringPtr = &data[0];
while (*currStringPtr != L'\0')
{
  // Current string is NUL-terminated, so get its length with wcslen
  const size_t currStringLength = wcslen(currStringPtr);
  // Add current string to result vector
  result.push_back(std::wstring{ currStringPtr, currStringLength });
  // Move to the next string
  currStringPtr += currStringLength + 1;
}

Finally, the result vector<wstring> object can be returned to the caller:

return result;

This RegGetMultiString C++ wrapper can simply be invoked, like this:

vector<wstring> multiString = RegGetMultiString(
  HKEY_CURRENT_USER,
  subkey,
  L"MyMultiSz"
);

Again, all the complexity of the Win32 RegGetValue API has been hidden behind a high-level convenient C++ interface.

Enumerating Values Under a Registry Key

Another common Windows registry operation is the enumeration of the values under a given registry key. Windows provides the RegEnumValue API (bit.ly/2jB4kaV) for this purpose. Here, I’ll show how to use this API to get a list of the names and types of the values located under a given registry key, wrapping the enumeration process in a convenient higher-level C++ function. Your custom C++ function can take as input a valid HKEY handle associated to the key you want to enumerate. On success, this custom C++ wrapper function will return a vector of pairs: The first item in the pair will be a wstring containing the value name and the second item a DWORD representing the value type. So, the prototype of this C++ wrapper function will look like this:

std::vector<std::pair<std::wstring, DWORD>> RegEnumValues(HKEY hKey)

Now I’ll discuss the details of the enumeration process. The idea is to first call the RegQueryInfoKey (bit.ly/2jraw2H) API to get some useful pre-enumeration information, like the total value count and the maximum length of value names under the given registry key, as shown in Figure 1.

Figure 1 Invoking the RegQuery­InfoKey API

DWORD valueCount{};
DWORD maxValueNameLen{};
LONG retCode = ::RegQueryInfoKey(
  hKey,
  nullptr,    // No user-defined class
  nullptr,    // No user-defined class size
  nullptr,    // Reserved
  nullptr,    // No subkey count
  nullptr,    // No subkey max length
  nullptr,    // No subkey class length
  &valueCount,
  &maxValueNameLen,
  nullptr,    // No max value length
  nullptr,    // No security descriptor
  nullptr     // No last write time
);

Note that I passed nullptr for the pieces of information in which I’m not interested. Of course, you have to check the return value and throw an exception if something went wrong when calling the aforementioned API:

if (retCode != ERROR_SUCCESS)
{
  throw RegistryError{"Cannot query key info from"
    the registry", retCode};
}

According to the Windows Dev Center RegQueryInfoKey function page (bit.ly/2lctUDt), the size returned for the maximum length of value names (stored in the maxValueNameLen variable in the previous code) does not include the terminating NUL; so, let’s adjust this value, adding one to take the terminating NUL into account when you allocate a buffer for reading value names:

maxValueNameLen++;

Then you can allocate a buffer of proper size to read the value names at each enumeration step; an efficient low-overhead std::unique_ptr<wchar_t[]> can be used for that purpose:

auto nameBuffer = std::make_unique<wchar_t[]>(maxValueNameLen);

The result of the enumeration, in the form of pairs of value name and value type, can be stored in a std::vector:

std::vector<std::pair<std::wstring, DWORD>> values;

You’ll progressively add content to this vector during the enumeration process and then “values” will be returned to the caller when the enumeration is complete.

Then you can use a for loop, calling the RegEnumValue API repeatedly and enumerating a new value at each iteration step:

for (DWORD index = 0; index < valueCount; index++)
{
  // Call RegEnumValue to get data of current value ...
}

Note that you got valueCount from the initial pre-enumeration RegQueryInfoKey call.

Inside the body of the for loop, the RegEnumValue API can be called to get the desired information for the current value. In this context, you’re interested in the value’s name and value’s type. The value’s name will be read in the nameBuffer previously allocated; the value’s type will be stored in a simple DWORD. So, inside the body of the for loop, you can write code like this:

DWORD valueNameLen = maxValueNameLen;
DWORD valueType{};
retCode = ::RegEnumValue(
  hKey,
  index,
  nameBuffer.get(),
  &valueNameLen,
  nullptr,    // Reserved
  &valueType,
  nullptr,    // Not interested in data
  nullptr     // Not interested in data size

As usual, it’s good practice to check the API return value and throw an exception on error:

if (retCode != ERROR_SUCCESS)
{
  throw RegistryError{"Cannot get value info from the registry", retCode};
}

On success, the RegEnumValue API will write the value’s name in the provided nameBuffer, and the value’s type in the valueType variable. So, you can build a pair<wstring, DWORD> with these two pieces of information and add this information pair to the enumeration result vector:

values.push_back(std::make_pair(
  std::wstring{ nameBuffer.get(), valueNameLen },
  valueType
));

After the for loop, the result “values” vector can be returned to the caller:

return values;

The caller can then enumerate all the values under a registry key by just calling the C++ wrapper function like this:

auto values = RegEnumValues(hKey);
// For each value
for (const auto& v : values)
{
  // Process v.first (value's name) and v.second (value's type)
  // ...
}

A similar coding pattern can be used to enumerate the sub-keys under a given registry key; in this case, the Win32 RegEnumKeyEx (bit.ly/2k3VEX8) API must be used instead of the previously discussed RegEnumValue. The code of such sub-key enumeration function is provided in the download associated with this article.

A Safe Resource Manager for Raw HKEY Handles

Registry keys represented by the raw HKEY Win32 handle type can be safely and conveniently wrapped in a C++ resource manager class. The class destructor will properly call the RegCloseKey API on the wrapped raw handle to automatically close the handle. Moreover, move semantics operations like a move constructor and a move assignment operator can be defined to efficiently transfer ownership of the wrapped handle between different instances of the C++ resource manager class. For efficiency, all the class methods that don’t throw exceptions are marked as noexcept, letting the C++ compiler emit more optimized code. This convenient key resource manager C++ class, named RegKey, is implemented in the Registry.hpp file accompanying this article. In this reusable header-only file, you’ll also find the implementations of a couple of helper functions: RegOpenKey and RegCreateKey, that respectively wrap the Win32 APIs RegOpenKeyEx and RegCreateKeyEx, returning an HKEY handle safely wrapped in the aforementioned C++ resource manager class. In case of errors, those C++ functions throw a RegistryError exception, wrapping the error code returned by the raw C-interface Win32 APIs.

Wrapping Up

The RegGetValue Win32 API provides a relatively higher-level interface for reading values from the Windows registry when compared to lower-level APIs such as RegQueryValueEx. RegGetValue also offers a safer interface that, for example, guarantees the returned strings are properly NUL-terminated. Nonetheless, RegGetValue is still a C-interface low-level API that requires the programmer to pay attention to many details, and programming against it can lead to bug-prone complex code. This article showed how a convenient, easy-to-use and hard-to-misuse modern C++ interface can be built to hide the complexities of the RegGetValue API, while simplifying access to the Windows registry. Moreover, the RegEnumValue API was wrapped in a convenient higher-level C++ function to enumerate all the values under a given registry key. The source code containing the implementation of the functions and classes discussed in this article can be found in a reusable header-only form (in the Registry.hpp file) in the article’s download.


Giovanni Dicanio is a computer programmer specializing in C++ and the Windows OS, a Pluralsight author (bit.ly/GioDPS) and a Visual C++ MVP. Besides programming and course authoring, he enjoys helping others on forums and communities devoted to C++. Reach him via e-mail at giovanni.dicanio@gmail.com. He also blogs on msmvps.com/gdicanio.

Thanks to the following technical experts for reviewing this article: David Cravey and Marc Gregoire
David Cravey is an Enterprise Architect at GlobalSCAPE, leads several C++ user groups, and was a four-time Visual C++ MVP.

Marc Gregoire is a senior software engineer from Belgium, the founder of the Belgian C++ Users Group, author of “Professional C++” (Wiley), co-author of “C++ Standard Library Quick Reference” (Apress), technical editor on numerous books, and since 2007, has received the yearly MVP award for his VC++ expertise. Marc can be contacted at marc.gregoire@nuonsoft.com.


Discuss this article in the MSDN Magazine forum