Accessing And Enumerating Existing Keys
There are two ways of retrieving the information stored in registry keys: You can read the keys individually or you can enumerate them, looping until a particular key’s supply of subkeys is exhausted. You can see examples of both methods in the sample code from our last installment, RegDemo.cpp. Notice how we initialize the dialog in HkeyLocalSubKeysDlg(). First, take a look at the call to RegOpenKeyEx():
case WM_INITDIALOG: //open key to gain access to subkeys rc = RegOpenKeyEx( HKEY_LOCAL_MACHINE, TEXT (""), 0, KEY_ENUMERATE_SUB_KEYS, &hKeyN2);
The parameters, in the order shown, are the handle to the parent key, the name of the key we want to open, the requested access mode to the key we are opening, and the address of the variable to receive the returned handle. As you saw earlier, it isn’t strictly necessary to open the HKEY_LOCAL_MACHINE root key. Notice two things here, however: In the second parameter, we set an empty subkey name; and in the next parameter, we set the access mode KEY_SET_VALUE. The value of the access mode determines what you can do with and to the opened key in subsequent manipulations using the returned handle. Here is a list of key access modes and their meanings.
Figure 7—Key Access Modes
Access Mode Constant | Effect of Setting this Access Mode |
KEY_ALL_ACCESS | Equivalent to KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS | KEY_NOTIFY | KEY_CREATE_SUB_KEY | KEY_CREATE_LINK | KEY_SET_VALUE |
KEY_CREATE_LINK | Allows creation of a symbolic link |
KEY_CREATE_SUB_KEY | Allows creation of subkeys |
KEY_ENUMERATE_SUB_KEYS | Allows enumeration of subkeys |
KEY_EXECUTE | Allows read access |
KEY_NOTIFY | Allows change notification |
KEY_QUERY_VALUE | Allows query of subkey data |
KEY_READ | Equivalent to KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS | KEY_NOTIFY |
KEY_SET_VALUE | Allows write of subkey data |
KEY_WRITE | Equivalent to KEY_SET_VALUE | KEY_CREATE_SUB_KEY |
As a matter of defensive programming, it’s best to set an access mode that allows no more than the capability required to accomplish what you intend.
If we manage to open the root key successfully, we set up to enumerate its sub keys. First, recall that at the entry to the HkeyLocalSubKeysDlg() function, we initialize the variable i:
INT i = 0, rc;
Next, we check the return from the key open call to make sure we don’t use an invalid handle in the subsequent calls.
if (rc == ERROR_SUCCESS) {
We initialize the parameters that give the size of the arrays for the key name and the key’s class using the dim macro we defined at the top of this source file. Included below is the definition of dim:
//a handy macro for finding array dimensions #define dim(x) (sizeof(x) / sizeof(x[0])) dwNSize = dim(szName); dwCSize = dim(szClass);
Now, we begin the enumeration at the key with an index of 0:
//set up enumeration of the key's subkeys rc = RegEnumKeyEx (HKEY_LOCAL_MACHINE, i, szName, &dwNSize, NULL, szClass, &dwCSize, NULL);
The parameters to RegEnumKeyEx(), in the order shown, are the handle to the key we are enumerating, the key’s index, an array of TCHARS to hold the enumerated key’s name, the size of this array in characters, including the terminal null, a NULL placeholder, an array of TCHARS to hold the enumerated key’s class name, the size of the class buffer, and a final NULL place holder. Before we go on, note the usefulness of the dim macro here. You can’t use sizeof() to set the buffer sizes in this call, because that returns the size in bytes, which is not the same as the size in characters when strings are in Unicode.
Porting Tip: If registry access code shows symptoms of memory overwrites, double check function calls that have buffer size parameters in character counts. Make sure sizes are expressed as Unicode character counts that include a terminal null.
As we continue the enumeration, we retrieve key names but ignore their class name information. We do this by setting the class name buffer and its size to NULL.
while (rc == ERROR_SUCCESS) { ListBox_InsertString(hWndList, i, szName); dwNSize = dim(szName); rc = RegEnumKeyEx (HKEY_LOCAL_MACHINE, ++i, szName, &dwNSize, NULL, NULL, 0, NULL); }
When we are done enumerating the subkeys, we close the handle we opened above.
//clean up now RegCloseKey( hKeyN2 ); } //end if( ERROR_SUCCESS )
Adding Values to Keys
In order to add values to a key, you must open it with at least KEY_SET_VALUE access permissions:
case IDM_ADD_SINGLE_VALUE: //open the key to gain access to the value rc = RegOpenKeyEx (HKEY_LOCAL_MACHINE, TEXT ("Softwaren2"), 0, KEY_SET_VALUE, &hKeyN2);
If all goes well, you are presented with great flexibility in regards to what gets stored in the value. Here are the most useful key types supported by Windows CE:
Figure 8—Windows CE Registry Key Data Types
Registry Key Data Types | Usage |
REG_BINARY | Byte string data |
REG_DWORD | A 32-bit number |
REG_DWORD_LITTLE_ENDIAN | A 32-bit number in little-endian format. Same as REG_DWORD |
REG_DWORD_BIG_ENDIAN | A 32-bit number in big-endian format |
REG_MULTI_SZ | An array of null-terminated strings, terminated by two null characters |
REG_SZ | A null-terminated string |
You should use the smallest registry type that will accommodate the data you plan to store. In this case, we add a single, named value of type REG_DWORD. At the top of the function, we initialized the DWORD dwRegVal in its declaration.
DWORD dwRegVal = 0x1234; //set a name and value under the open key rc = RegSetValueEx (hKeyN2, TEXT("OneDWORD"), 0, REG_DWORD , (LPBYTE)&dwRegVal, sizeof(dwRegVal));
The parameters to RegSetValueEx (), in the order shown, are the handle to the key receiving the value, the name of the value, the type, the address of the first byte of the buffer where the data is stored, and the size of the data in bytes. Notice that we use sizeof() to set the buffer size in this case. This is a good practice, because if the data in this key were one of string types, sizeof() would automatically account for the space taken up by a NULL terminator.
Finally, skipping over the message box code, we close the key.
//always clean up after ourselves RegCloseKey( hKeyN2 ); break;
What if you have several items of registry data under a key? You could add a named value for each item, but that approach would use a lot of space to store the value names and require a registry access to get or set each value. If your data are all strings, you can use the REG_MULTI_SZ type, and combine all of the strings into a single named value. If the data are of mixed type, consider aggregating them and storing them in a single binary valued key. Your application can retrieve the array in a single registry access and parse them much more quickly than if they were retrieved individually. Here is how we set a binary array as a registry value in RegDemo:
//set the binary value rc = RegSetValueEx (hKeyN2, TEXT ("Aggregate"), 0, REG_BINARY , (PBYTE)pBuff, LocalSize( (HLOCAL)pBuff ));
The Buffer identified by pBuff was allocated by LocalAlloc(), so the most reliable way of getting its size is with LocalSize(). The data is written to the registry as a string of bytes, which exactly preserves the order of the bytes as they were written. This means that if your application stores a mixture of types (int, float, char, and so forth) in the byte array, you’ll have to take care to read the items out of the string using pointers that are properly cast to the types of data being transferred.
Enumerating Registry Values
By its nature, the content of the registry is somewhat dynamic. Used in the conservative fashion, the values of your application’s registry keys will depend on the state the application was in when it was last used. Keys have a possible set of values, which may or may not be present. To find out which values are present, you use RegEnumValue() to enumerate the value set of a key. In the RegDemo example, we do this in the HKeyN2ValuesDlg(). Let’s dissect the value enumeration process.
First, you must open the key that owns the value with at least KEY_ENUMERATE_SUB_KEYS access permission.
rc = RegOpenKeyEx ( HKEY_LOCAL_MACHINE, TEXT ("Softwaren2"), 0, KEY_ENUMERATE_SUB_KEYS , &hKeyN2);
If we successfully open the key, we set up for enumeration. Notice that value of the variable i was initialized at the top of the function:
INT i = 0, rc;
Next, we initialize dwNSize with the size, in Unicode characters, of the value name buffer. We initialize dwDSize with the size, in bytes, of the value data buffer.
//set up to enumerate values dwNSize = dim(szName); dwDSize = sizeof(bData);
Now, we make the initial call to RegEnumValue:
rc = RegEnumValue(hKeyN2, i, szName, &dwNSize, NULL, &dwType, (PBYTE)&bData, &dwDSize );
The parameters, in the order shown, are the handle to the key that owns this value, the index of the value to retrieve, a buffer to hold the returned name of the value, the size of the buffer in Unicode characters, including the terminal null, a NULL place holder, the address of a DWORD in which the registry data type of this value is returned, the address of the data buffer, and the size of the data buffer in bytes. We capture the return of this call in the variable rc, and test it to determine when we have reached the end of the value list for this key. We loop over all of the values in order to initialize the list box:
while (rc == ERROR_SUCCESS) {
First, we insert the value name. The name was returned in a Unicode string, and so can be directly inserted into the list box.
ListBox_InsertString(hWndList, -1, szName);
The key data is returned as a byte string, and is essentially “typeless” when we get it. In order to use the data, we have to discriminate its type, and then use a type cast to correctly translate the bytes into the proper format for their given type.
//distinguish between key types memset( bBuffer, 0x0, sizeof(bBuffer)); if( dwType == REG_BINARY ) { wsprintf( (LPTSTR)&bBuffer,TEXT(" %s Data:%x "), TEXT("REG_BINARY"), *bData ); }
In the call to wsprintf() shown above, we print the first byte of the data buffer, two hex digits, into the string that will be inserted into the list box. In this case, we simply provide the address of the first byte of the data buffer. If we wanted to print more of the hex digits, we could use a call such as this:
//just a code fragment, not part of RegDemo wsprintf( (LPTSTR)&bBuffer,TEXT(" %s Data:%x%x%x"), TEXT("REG_BINARY"), *bData, *bData[1], *bData[2] );
When we want to manipulate other registry data types, we need to take a little more care.
if( dwType == REG_DWORD ) { dwDatum = (DWORD)(bData); iTest = (int)dwDatum; wsprintf( (LPTSTR)&bBuffer, TEXT(" %s Data:%x "), TEXT("REG_DWORD"), *(int*)bData ); }
Notice that in the wsprintf() call that displays the REG_DWORD data, we first cast the address of the data buffer to an integer pointer, and then dereference that pointer. Most numeric types are stored in memory with the low order bytes stored at the lowest address ( LOWORD/HIWORD). For this reason, if you simply copy the correct number of bytes out of the data buffer and into a variable, you won’t get the correct value.
ListBox_InsertString(hWndList, -1, bBuffer); dwNSize = dim(szName); dwDSize = sizeof(bData); rc = RegEnumValue(hKeyN2, ++i, szName, &dwNSize, NULL, &dwType, (PBYTE)&bData, &dwDSize );
We increment the variable i on each call to RegEnumValue() and continue looping until the function return indicates there are no more values.
Looking Ahead
In the next installment, we’ll learn how to manipulate individual key values by adding, modifying and deleting them.
About the Author
Nancy Nicolaisen is a software engineer who has designed and implemented highly modular Windows CE products that include features such as full remote diagnostics, CE-side data compression, dynamically constructed user interface, automatic screen size detection, and entry time data validation.
In addition to writing for Developer.com, she has written several books, including Making Win 32 Applications Mobile.
# # #