Login

 

Site Menu

I often get some fun challenges from coders looking for help in the #winapi channel I frequent on freenode. The coder today was looking for a way to get the output from RegEnumValue and values of type REG_SZ into a vector. Start by creating a Win32 project. In your stdafx.h here's what we need to include:

 

1
2
3
4
#include <windows.h>
#include <iostream>
#include <string>
#include <vector>

Next we need a structure that's used to hold the name and data for each registry value. You could make this more generic to handle other types such as a vector of bytes or even turn this into a class with functions that will convert the data into the format you want. I'm keeping it simple and to the point, we're after the strings. Notice I'm using unicode here (explicitly).

1
2
3
4
struct REG_STRINGVALUE { 
  std::wstring name; 
  std::wstring data; 
};

The next part is the biggest, the part where we do all of the work! Again I am using unicode explicitly so it's not going to be RegEnumValue, but RegEnumValueW. You can easily convert this to multibyte (but why would you?) by using char instead of wchar_t and std::string instead of std::wstring. Here's the code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
bool GetRegistryStringValues(HKEY hKey, std::vector<REG_STRINGVALUE>& output)
{
    /* Clear our output parameter: */
    output.clear();
 
    /* Value name buffer & max registry value name length (MSDN states is 32767): */
    const DWORD value_name_max = 32767;
    std::vector<wchar_t> value_name_buffer(value_name_max);
 
    DWORD value_name_length = value_name_max;
    DWORD value_data_length = 0;
    DWORD value_type = REG_NONE; /* This gets set by RegEnumValue. */
    LONG result = 0;
    DWORD index = 0;
    while((result = ::RegEnumValueW(hKey, index, value_name_buffer.data(), &value_name_length, NULL, &value_type, NULL, &value_data_length)) != ERROR_NO_MORE_ITEMS) {
        if(result != ERROR_SUCCESS) break;
        else {
            /* Only get values that are strings. */
            if(value_type == REG_SZ) {
                std::vector<wchar_t> value_data_buffer(value_data_length + 1);
                value_name_length = value_name_max;
 
                result = ::RegEnumValueW(hKey, index, value_name_buffer.data(), &value_name_length, NULL, &value_type, (LPBYTE)value_data_buffer.data(), &value_data_length);
                if(result != ERROR_SUCCESS) break;
 
                /* We need to make sure the end of the name and data end with \0. RegEnumValue does not do this for us. */
                value_name_buffer[value_name_length] = L'\0';
                value_data_buffer[value_data_length] = L'\0';
 
                /* Setup the structure and convert the vectors (buffers) into usable strings: */
                REG_STRINGVALUE item = { std::wstring(value_name_buffer.data()), std::wstring(value_data_buffer.data()) };
                int size = item.name.capacity();
                output.emplace_back(item);
            }
 
            /* Increment and Reset: */
            index++;
            value_name_length = value_name_max;
            value_data_length = 0;
        }
    }
 
    // Return result:
    if(result == ERROR_SUCCESS || result == ERROR_NO_MORE_ITEMS) return true;
    else return false;
}

Now you might ask why would I allocate 32 KB for the value name? Why not allocate just the amount of bytes that is needed like we do for the value data? Well, RegEnumValue doesn't support getting the size of the value name without copying it to a buffer like it does the data. If you try and set the third parameter () to NULL RegEnumValue will return 87 (ERROR_INVALID_PARAMETER) and your value name length will be unchanged. For the sake of performance and for the reason above we allocate the maximum amount of characters a value name could be (as specified in MSDN) only once before the loop.

Why am I using vectors for buffers? It's an old practice of mine and I'm sure there are even more modern ways to do this. If you have one comment below. Wherever I can, I want to have my types automatically delete themselves when they go out of scope. This can save you a lot of trouble. Now, I don't have to remember to delete anything wherever the loop ends. When a vector goes out of scope, it will deallocate what it has.

And if you're interested in the implementation (you may have to change the registry key), here yah go:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int _tmain(int argc, _TCHAR* argv[])
{
    HKEY hKey = NULL;
    if(::RegOpenKeyW(HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Group Policy\\GroupMembership", &hKey) != ERROR_SUCCESS)
        std::cout << "Failed in RegOpenKeyW()." << std::endl;
    else std::cout << "RegOpenKeyW Succeeded." << std::endl;
 
    std::vector<REG_STRINGVALUE> string_values;
    if(!GetRegistryStringValues(hKey, string_values)) {
        std::cout << "Failed in GetRegistryStringValues()." << std::endl;
    }
    else {
        std::cout << "GetRegistryStringValues() Succeeded." << std::endl;
        for(auto i = string_values.begin(); i != string_values.end(); i++) {
            std::wcout << L"NAME: " << (*i).name << L"\n" << L"DATA: " << (*i).data << L"\n\n";
        }
    }
 
    return 0;
}