Free VC++ Tutorial

Web based School

Previous Page Main Page Next Page


16 — The Registry

Ah, the Registry. This mysterious object that appeared with the introduction of OLE under Windows 3.1. No matter how hard we programmer types were trying to ignore it, it is here to stay; in fact, while we were looking the other way it quietly took over the role of initialization, or INI files, among other things.

But what is it? Perhaps more importantly, what should you, the Win32 programmer, know about it? How can you access and manipulate it from within your applications? These are the questions that I attempt to answer in this chapter.

Registry Structure

The Registry is a hierarchically organized store of information. Each entry in this tree-like information structure is called a key. A key may contain any number of subkeys; it can also contain data entries called values. In this form, the Registry stores information about the system, its configuration, hardware devices, and software applications. It also assumes the role of the ubiquitous INI files by providing a place where application-specific settings can be stored.

A Registry key is identified by its name. Keynames consist of printable ASCII characters except the backslash (\), space, and wildcard (* or ?) characters. The use of keynames that begin with a period (.) is reserved. Keynames are not case sensitive.

Registry Values

A value in the Registry is identified by its name. Value names consist of the same characters as keynames. The value itself can be a string, binary data, or a 32-bit unsigned value.

There are some apparent differences between the behavior of the Windows 95 and the Windows NT Registries. The Windows 95 Registry appears to allow for the assignment of a value to a Registry key (as opposed to a value name); this appears as the default value for that key. Upon closer examination, however, one finds that this is a superficial difference. The default value for a key is really a value with an empty name; empty names are also permitted in the Windows NT Registry. Perhaps the only difference is that the value with the empty name appears to be always defined for a key in the Windows 95 Registry, while it must be explicitly created in the Windows NT Registry.

Another apparent difference between the two Registries is the existence of a variety of string types in the Windows NT Registry, whereas Windows 95 appears to support only one string type. But is this really the case? Jumping a bit ahead of myself here, let me show you some of the output created by the Registry reader program that we examine later in this chapter. For example, look at the following output:

Enter key: HKEY_CURRENT_USER\Environment\include

Expandable string: f:\msvc20\include;f:\msvc20\mfc\include

However, if you examine the same value using the Windows 95 Registry Editor, it will appear as a binary value. But this is a shortcoming of the Registry Editor, not the Registry itself.

Table 16.1 contains a list of all value types that can go into the Windows 95 and Windows NT Registries.

    Table 16.1. Registry value types.
Symbolic Identifier


Description


REG_BINARY

Raw binary data

REG_DWORD

Double word in machine format (low-endian on Intel)

REG_DWORD_LITTLE_ENDIAN

Double word in little-endian format

REG_DWORD_BIG_ENDIAN

Double word in big-endian format

REG_EXPAND_SZ

String with unexpanded environment variables

REG_LINK

Unicode symbolic link

REG_MULTI_SZ

Multiple strings ended by two null characters

REG_NONE

Undefined type

REG_RESOURCE_LIST

Device-driver resource list

REG_SZ

Null-terminated string

Registry Capacity

Generally, it is not recommended to store items larger than a kilobyte or two in the Registry. For larger items, use a separate file, and use the Registry for storing the filename.

Under Windows 95, Registry values are limited to 64KB in size.

Another consideration when using the Registry is that storing a key generally requires substantially more storage space than storing a value. Whenever possible, organize values under a common key rather than using several keys for the same purpose.

Predefined Registry Keys

The Registry contains several predefined keys.

The HKEY_LOCAL_MACHINE key contains entries that describe the computer and its configuration. This includes information about the processor, system board, memory, and installed hardware and software.

The HKEY_CLASSES_ROOT key is the root key for information relating to document types and OLE types. This key is a subordinate key to HKEY_LOCAL_MACHINE. (It is equivalent to HKEY_LOCAL_MACHINE\SOFTWARE\Classes.) Information that is stored here is used by shell applications such as the Program Manager, File Manager, or the Explorer, and by OLE applications.

The HKEY_USERS key serves as the root key for the default user preference settings as well as individual user preferences.

The HKEY_CLASSES_USER key is the root key for information relating to the preferences of the current (logged in) user.

Under Windows 95, there are two additional predefined keys. The HKEY_CURRENT_CONFIG key contains information about the current system configuration settings. This key is equivalent to a subkey (such as 0001) of the key HKEY_LOCAL_MACHINE\Config.

The HKEY_DYN_DATA key provides access to dynamic status information, such as information about plug and play devices.

The predefined keys and their relationships are illustrated in Figure 16.1.


Figure 16.1. Predefined Registry keys.

Manually Editing the Registry

The Registry can be manually edited using the Registry Editor. This program is named regedt32.exe under Windows NT and regedit.exe under Windows 95. The Windows NT program regedit.exe is a version of the Registry Editor that offers behavior similar to the 16-bit Windows Registry Editor. This program is not very useful when editing the Registry as it only sees a subset of Registry keys.

Figure 16.2 shows the Windows 95 Registry Editor in operation.


Figure 16.2. The Windows 95 Registry Editor.

Needless to say, using the Registry Editor is a last resort solution. Programmers may need to frequently access the Registry this way (for example, to remove keys that have been placed there by misbehaving applications that are under development). However, end users should never be required to manually change Registry settings.

Many Registry settings are controlled implicitly through configuration applications such as the Control Panel. Other Registry settings are created during application installation. OLE applications that have been created using AppWizard update their Registry settings every time they run.

Commonly Used Registry Keys

Information about Registry keys is often difficult to find. For this reason, I decided to collect information on some frequently used Registry keys here that are of interest to the programmer.

Subtrees in HKEY_LOCAL_MACHINE

Keys in HKEY_LOCAL_MACHINE contain information about the computer's software and hardware configuration. Of these, the Config and Enum subkeys are specific to Windows 95 and its plug and play capabilities. The Config subkey is where Windows 95 stores various hardware configurations; the Enum subkey contains Windows 95 bus enumerators that build the tree of hardware devices.

Both Windows 95 and Windows NT maintain the System subkey under HKEY_LOCAL_MACHINE. The System\CurrentControlSet subkey contains configuration information for services and device drivers.

Other subkeys in HKEY_LOCAL_MACHINE include Software and Classes. The Software subkey is where information about installed software packages can be found. The Classes subkey is where HKEY_CLASSES_ROOT points to.

The Software subtree is of peculiar interest to application programmers. This is where you should store configuration and installation information specific to your application. Microsoft recommends that you build a series of subtrees under HKEY_LOCAL_MACHINE\Software. These subkeys should represent your company name, the name of your product, and the product's version number:

HKEY_LOCAL_MACHINE\Software\CompanyName\ProductName\1.0

For example, configuration information pertaining to the version of Microsoft guideshelf that is installed on my computer can be found under the following key:

HKEY_LOCAL_MACHINE\Software\Microsoft\guideshelf '95\95.0.0.39

What you store under such a key is entirely application-dependent. Note that you should not store anything here that is user-specific; user-specific information pertinent to your application should be organized under a subkey of HKEY_CURRENT_USER.

Of particular interest is the key

HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion

which describes the current Windows configuration. Another important key is

HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion

This key actually has a curious, unexpected role under Windows 95. I presume the reason is to maintain compatibility with 32-bit debuggers originally written for Windows NT, but debugger information stored under the key

HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Aedebug

will affect debugger behavior under Windows 95.

Subtrees in HKEY_CLASSES_ROOT

The HKEY_CLASSES_ROOT key contains two types of subkeys: subkeys that correspond to filename extensions and class definition subkeys.

A filename extension subkey has a name that corresponds to the filename extension (such as .doc). The key typically contains one unnamed value, which holds the name of the class definition subkey.

A class definition subkey describes the behavior of a document class. The information stored here includes data on shell and OLE-related properties.

A subkey under HKEY_CLASSES_ROOT is CLSID. This is the place where OLE class identifiers are stored.

When you create an MFC application using the Visual C++ AppWizard, a series of subkeys that are to be installed under HKEY_CLASSES_ROOT are also created. These identify the document type and filename extension of your new application and also its OLE properties such as the OLE class identifier. For example, creating an MFC application named Test with a file extension .tst for its document files yielded the following Registry entries under HKEY_CLASSES_ROOT:

.TST = Test.Document

Test.Document\shell\open\command = TEST.EXE %1

Test.Document\shell\open\ddeexec = [open("%1")]

Test.Document\shell\open\ddeexec\application = TEST

Test.Document = Test Document

Test.Document\protocol\StdFileEditing\server = TEST.EXE

Test.Document\protocol\StdFileEditing\verb\0 = &Edit

Test.Document\Insertable =

Test.Document\CLSID = {FC168A60-F1EA-11CE-87C3-00403321BFAC}

The following entries were also created under HKEY_CLASSES_ROOT\CLSID:

{FC168A60-F1EA-11CE-87C3-00403321BFAC} = Test Document

{FC168A60-F1EA-11CE-87C3-00403321BFAC}\DefaultIcon = TEST.EXE,1

{FC168A60-F1EA-11CE-87C3-00403321BFAC}\LocalServer32 = TEST.EXE

{FC168A60-F1EA-11CE-87C3-00403321BFAC}\ProgId = Test.Document

{FC168A60-F1EA-11CE-87C3-00403321BFAC}\MiscStatus = 32

{FC168A60-F1EA-11CE-87C3-00403321BFAC}\AuxUserType\3 = test

{FC168A60-F1EA-11CE-87C3-00403321BFAC}\AuxUserType\2 = Test

{FC168A60-F1EA-11CE-87C3-00403321BFAC}\Insertable =

{FC168A60-F1EA-11CE-87C3-00403321BFAC}\verb\1 = &Open,0,2

{FC168A60-F1EA-11CE-87C3-00403321BFAC}\verb\0 = &Edit,0,2

{FC168A60-F1EA-11CE-87C3-00403321BFAC}\InprocHandler32 = ole32.dll

Subtrees in HKEY_USERS

The key HKEY_USERS contains a subkey named .Default and zero or more subkeys corresponding to users on the system. The .Default subkey corresponds to the default user profile. Other entries correspond to profiles of existing users.

Subtrees in HKEY_CURRENT_USER

The HKEY_CURRENT_USER key corresponds to the profile of the currently logged in user. This key has several subkeys, some common to both Windows 95 and Windows NT, some specific to one or the other.

Application configuration information specific to the current user should be stored under the subkey Software. Information should be organized by keys corresponding to company name, product name, and product version number. For example, user settings for Microsoft Excel 5.0 can be found under the key

HKEY_CURRENT_USER\Software\Microsoft\Excel\5.0

The user's settings and preferences for Windows, its components, and applets can be found under the key

HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion

and its subkeys.

The Registry and INI Files

In new applications, the Registry should be used instead of INI files. This is obvious; but what about old applications, how would they behave under 32-bit Windows?

As it turns out, their behavior is different under Windows NT and Windows 95. In order to maintain maximum backward compatibility, Windows 95 still maintains INI files, such as a win.ini file or a system.ini file. These files do not exist under Windows NT. Instead, Windows NT maps these files to the Registry.

Which files are mapped and which are not is determined by the settings under the key

SOFTWARE\Microsoft\Windows NT\CurrentVersion\IniFileMapping

This key contains a subkey for every mapped INI file. Values under such a subkey correspond to sections in the INI file and typically point to other keys in the Registry.

The mapping of INI files affects the operation of functions such as ReadProfileString or WriteProfileString. If a mapping exists for the specified INI file, these functions will read from and write to the Registry as opposed to an actual INI file.

Application Programs and the Registry

The Win32 API offers a variety of functions for manipulating the Registry.

Opening a Registry Key

All access to the Registry is performed through handles. In order to access a key in the Registry, applications must use a handle to an existing, open key. There are several predefined key handles that are assumed to be always open. These handles include the following: HKEY_LOCAL_MACHINE, HKEY_CLASSES_ROOT, HKEY_USERS, HKEY_CURRENT_USER.

A Registry key is accessed through the function RegOpenKeyEx. For example, in order to obtain a handle to the Registry key HKEY_LOCAL_MACHINE\Software, one would issue the following call:

RegOpenKeyEx(HKEY_LOCAL_MACHINE, "Software", 0, KEY_READ, &hKey);

To access a subkey under the key HKEY_LOCAL_MACHINE\Software, it is necessary to call RegOpenKeyEx again. For example, to obtain a handle to HKEY_LOCAL_MACHINE\Software\Classes, one would have to issue the following two calls:

RegOpenKeyEx(HKEY_LOCAL_MACHINE, "Software", 0, KEY_READ, &hKey);

RegOpenKeyEx(hKey, "Classes", 0, KEY_READ, &hSubKey);

Logical as it may appear, it is not possible to use concatenated key values delimited by a backslash as the keyname parameter to RegOpenKeyEx. Thus, the following call is an error:

RegOpenKeyEx(hKey, "Key\\Subkey", 0, KEY_READ, &hSubKey); // ERROR!

When an application is finished using a Registry key, it should close the key by calling RegCloseKey.

Querying a Value

A Registry value can be retrieved by calling the RegQueryValueEx function. Before this function can be called, the appropriate subkey must be opened using RegOpenKey.

RegQueryValueEx offers a mechanism that enables applications to find out the memory requirements for storing a value before the value is actually retrieved. If you call this function with a NULL pointer passed as the data buffer pointer, the function will return the requested length of the data buffer without actually copying any data. Thus, it is possible to call RegQueryValueEx twice: first to obtain the length of the buffer, and next to actually copy the data, as in the following example:

RegQueryValueEx(hKey, "MyValue", NULL, &dwType, NULL, &dwSize);

pData = malloc(dwSize);

RegQueryValueEx(hKey, "MyValue", NULL, &dwType, pData, &dwSize);

Setting a Value

A value in the Registry can be set using the RegSetValueEx function. Before this function can be used, the appropriate subkey must be opened with KEY_SET_VALUE access using RegOpenKeyEx.

Creating a New Key

Applications can also create a new subkey in the Registry. The RegCreateKeyEx function creates the new key, opens it, and obtains a key handle. This function can also be used to open existing keys; thus it is ideal in situations when the application wishes to access a key whether it already exists or not—during an installation procedure, for example.

Under Windows NT, when creating a new key, the application also assigns security attributes to it. The key's security attributes determine who can access the key for reading and writing. Security information can be obtained about an open key using RegGetKeySecurity and set using RegSetKeySecurity (that is, if the application has the necessary privileges).

Other Registry Functions

There are several other functions that assist in dealing with the Registry efficiently. For example, the RegEnumKeyEx and RegEnumValue functions can be used to enumerate the subkeys and values under a specific Registry key. Registry keys can be deleted using the RegDeleteKey function. Several other functions exist to deal with saving and loading subkeys, connecting to remote Registries, and performing other administrative functions.

A Working Example

To demonstrate the use of the Registry from application programs, I decided to create a simple command-line program to read Registry settings. This program is shown in Listing 16.1. To compile this program from the command line, you must specify the advanced API library: cl readreg.cpp advapi32.lib.

    Listing 16.1. A simple Registry reader.
#include <windows.h>

#include <iostream.h>

#include <iomanip.h>

#include <string.h>

#define STR_HKEY_LOCAL_MACHINE "HKEY_LOCAL_MACHINE"

#define STR_HKEY_CLASSES_ROOT "HKEY_CLASSES_ROOT"

#define STR_HKEY_USERS "HKEY_USERS"

#define STR_HKEY_CURRENT_USER "HKEY_CURRENT_USER"

#define LEN_HKEY_LOCAL_MACHINE (sizeof(STR_HKEY_LOCAL_MACHINE)-1)

#define LEN_HKEY_CLASSES_ROOT (sizeof(STR_HKEY_CLASSES_ROOT)-1)

#define LEN_HKEY_USERS (sizeof(STR_HKEY_USERS)-1)

#define LEN_HKEY_CURRENT_USER (sizeof(STR_HKEY_CURRENT_USER)-1)

#define SWAP_ENDIAN(x) (((x<<24)&0xFF000000)|((x<<8)&0xFF0000)|\

                       ((x>>8)&0xFF00)|((x>>24)|0xFF))

void printval(unsigned char *pBuffer, DWORD dwType, DWORD dwSize)

{

    switch (dwType)

    {

        case REG_BINARY:

            cout << "Binary data:";

            {

                for (unsigned int i = 0; i < dwSize; i++)

                {

                    if (i % 16 == 0) cout << '\n';

                    cout.fill('0');

                    cout << hex << setw(2) <<

                            (unsigned int)(pBuffer[i]) << ' ';

                }

            }

            cout << '\n';

            break;

        case REG_DWORD:

            cout.fill('0');

            cout << "Double word: " << hex << setw(8) <<

                    *((unsigned int *)pBuffer) << '\n';

            break;

        case REG_DWORD_BIG_ENDIAN:  // Intel specific!

            cout.fill('0');

            cout << "Big-endian double word: " << hex << setw(8) <<

                   SWAP_ENDIAN(*((unsigned int *)pBuffer)) << '\n';

            break;

        case REG_EXPAND_SZ:

            cout << "Expandable string: " << pBuffer << '\n';

            break;

        case REG_LINK:

            cout << "Unicode link.";

            break;

        case REG_MULTI_SZ:

            cout << "Multiple strings:\n";

            {

                char *pStr;

                int i;

                for (i = 0, pStr = (char *)pBuffer; *pStr != '\0';

                     i++, pStr += strlen((char *)pStr) + 1)

                {

                    cout << "String " << i << ": " << pStr << '\n';

                }

            }

            break;

        case REG_NONE:

            cout << "Undefined value type.\n";

            break;

        case REG_RESOURCE_LIST:

            cout << "Resource list.\n";

            break;

        case REG_SZ:

            cout << "String: " << pBuffer << '\n';

            break;

        default:

            cout << "Invalid type code.\n";

            break;

    }

}

void main(void)

{

    char szKey[1000];

    char *pKey;

    HKEY hKey, hSubKey;

    DWORD dwType;

    DWORD dwSize;

    unsigned char *pBuffer;

    int nKey;

    while (1)

    {

        cout << "Enter key: ";

        cin.getline(szKey, 1000);

        nKey = strcspn(szKey, "\\");

        hKey = NULL;

        if (!strncmp(szKey, STR_HKEY_LOCAL_MACHINE, nKey) &&

                 nKey == LEN_HKEY_LOCAL_MACHINE)

            hKey = HKEY_LOCAL_MACHINE;

        if (!strncmp(szKey, STR_HKEY_CLASSES_ROOT, nKey) &&

                 nKey == LEN_HKEY_CLASSES_ROOT)

            hKey = HKEY_CLASSES_ROOT;

        if (!strncmp(szKey, STR_HKEY_USERS, nKey) &&

                 nKey == LEN_HKEY_USERS)

            hKey = HKEY_USERS;

        if (!strncmp(szKey, STR_HKEY_CURRENT_USER, nKey) &&

                 nKey == LEN_HKEY_CURRENT_USER)

            hKey = HKEY_CURRENT_USER;

        if (hKey == NULL || szKey[nKey] != '\\')

        {

            cout << "Invalid key.\n";

            continue;

        }

        pKey = szKey + nKey + 1;

        nKey = strcspn(pKey, "\\");

        while (pKey[nKey] == '\\')

        {

            pKey[nKey] = '\0';

            if (RegOpenKeyEx(hKey, pKey, NULL, KEY_READ,&hSubKey)

                == ERROR_SUCCESS)

            {

                RegCloseKey(hKey);

                hKey = hSubKey;

            }

            else

            {

                RegCloseKey(hKey);

                hKey = NULL;

                break;

            }

            pKey += nKey + 1;

            nKey = strcspn(pKey, "\\");

        }

        if (hKey == NULL)

        {

            cout << "Invalid key.\n";

            continue;

        }

        if (RegQueryValueEx(hKey, pKey, NULL, &dwType, NULL,

                            &dwSize) == ERROR_SUCCESS)

        {

            pBuffer = (unsigned char *)malloc(dwSize);

            if (pBuffer == NULL)

            {

                cout << "Insufficient memory.\n";

                break;

            }

            if (RegQueryValueEx(hKey, pKey, NULL, &dwType, pBuffer,

                                &dwSize) == ERROR_SUCCESS)

                printval(pBuffer, dwType, dwSize);

            else

                cout << "Error reading key.\n";

            free(pBuffer);

        }

        else

            cout << "Error reading key.\n";

        RegCloseKey(hKey);

    }

}

This program demonstrates many aspects of handling the Registry. Execution begins in the main function where the program immediately enters a forever loop (terminate the program by hitting Ctrl+C). After displaying a prompt and reading in a Registry key typed by the user, the program first checks for the presence of the name of any of the top-level keys in the string the user typed. If such a keyname is found, the program begins an iteration.

The string typed by the user is expected to contain backslash characters as key delimiters. In the iteration part, subsequent strings are extracted from the input string using the strcspn function. The iteration proceeds until the last string is extracted, which is assumed to be a value name. The iteration allows for empty (zero-length) names for both keys and values.

During the iteration, a key handle is obtained through RegOpenKeyEx for every string extracted from the user input. If obtaining the key handle fails (presumably because the user specified an invalid key) the iteration stops with an error. However, if the iteration succeeds, the value corresponding to the last extracted string is retrieved by calling RegQueryValueEx. This function is in fact called twice: once to determine the amount of memory required to store the data and a second time to actually retrieve the data.

The value is printed in the printval function. This function takes a pointer to the value and takes the value's type and its length, from which it produces formatted output.

Here is some sample output produced by this program:

Enter key: HKEY_CURRENT_USER\Software\Microsoft\Hover!\HighScoreNames

Multiple strings:

String 0: Viktor

String 1: Steve

String 2: John

String 3: Patrick

String 4: Jennifer

String 5: Julie

String 6: Jon

String 7: Tony

String 8: Larry

String 9: Harley

Enter key: HKEY_LOCAL_MACHINE\Enum\Monitor\Default_Monitor\0001\ConfigFlags

Binary data:

00 00 00 00

Enter key: HKEY_CURRENT_USER\Environment\include

Expandable string: f:\msvc20\include;f:\msvc20\mfc\include

You may even find this program useful when trying to determine the exact type of a value in the Registry, above and beyond the information provided by the Registry Editor. With some work, the program could be modified to have the capability to actually write to the Registry as well.

Summary

The Registry is a place where Windows and applications can store configuration data. The Registry is a tree-like, hierarchically organized information store. Registry entries, or keys, are identified by a name and may contain any number of subkeys or values.

At the top level of the Registry are the root keys HKEY_USERS and HKEY_LOCAL_MACHINE. Other predefined keys include HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, HKEY_CURRENT_CONFIG, and HKEY_DYN_DATA.

A Registry value can be a 4-byte integer, a string or a series of strings, or arbitrary binary data. Registry values are usually created by application programs, installation procedures, or configuration utilities such as the Control Panel. However, the Registry can also be manually edited using the Registry Editor.

Applications typically store configuration information under HKEY_LOCAL_MACHINE\Software, and user-specific data under HKEY_CURRENT_USER\Software. In both cases, subkeys should be created to correspond to a company name, product name, and product version number.

Additionally, applications that manage specific document types created a filename extension and a class definition entry under HKEY_CLASSES_ROOT. OLE applications also store OLE information here.

The Win32 API provides a series of functions for applications to access the Registry. Using one of the predefined Registry keys, applications can access any subkey for reading and writing, query values, and set values. Applications can also create new keys or delete existing keys.

Previous Page Main Page Next Page