Team LiB   Previous Section   Next Section

8.3 Programming with C/C++

The API examples and documentation in earlier sections all present the Registry API in its native C/C++ form. Since many administrators are comfortable with C and/or C++, I'll start the programming examples by presenting three distinct uses for the Registry API routines I've already presented.

8.3.1 Example: Watching a Key for Changes

RegNotifyChangeKeyValue is a little-used, but very useful, routine. It's only present in Windows 2000 and NT, which perhaps accounts for its relative anonymity. If you need to be notified when a key or its values changes, it's the best tool for getting you that notification. WatchKey, shown in Example 8.4, is a small utility that takes advantage of RegNotifyChangeKeyValue to warn you when a key you specify has been changed.

8.3.1.1 How the code works

After a check of its initial command-line arguments, the code performs the following steps:

  1. It identifies which root key "owns" the key you want to monitor; this is required because RegOpenKeyEx needs an already open key (i.e., one of the roots) to open the target key. If it can't figure out which root the user specified, it prints an error and exits.

  2. It captures the pathname of the key to monitor and uses it, along with the root key, to call RegOpenKeyEx. The key is opened with KEY_READ access, which includes KEY_NOTIFY access too. If the key can't be opened, the code generates an error message and exits.

  3. The target key is monitored with a call to RegNotifyChangeKeyValue. The code passes TRUE for the bWatchSubtree parameter so that any change to a key or value beneath the target key generates a notification. For the dwNotifyFilter parameter, you pass all available event flags in so that any changes to the target key trigger a notification. No event handle is passed in, and the fAsynchronous parameter is set to TRUE so that the process blocks until a change occurs.

Example 8.4. The WatchKey Utility
// WatchKey.c
// Watches the key you specify until it changes, then displays the time and date
// when the change occurred.

#include <windows.h>
#include <stdio.h>
#include <time.h>


// error codes we generate
#define kBadParams    1
#define kNoRootKey    2
#define kCantOpenPath 3


static const HKEY hkRootList[5] = {HKEY_LOCAL_MACHINE, HKEY_CURRENT_USER, HKEY_USERS, 
HKEY_CURRENT_CONFIG, HKEY_CLASSES_ROOT};
static const char *pszRootNames[5] = {"HKLM", "HKCU", "HKU", "HKCC", "HKCR"};

void DoUsage(const char *inName);


void DoUsage(const char *inName)
{
    printf("%s: improper command-line parameters\n", inName);
    printf("\tUsage: %s root path\n", inName);
    printf("\t\troot\tRoot key to monitor; may be HKLM, HKCC, HKCR,
        HKU, or HKCU\n";
    printf("\t\tpath\tFull path to subkey you want to monitor\n");
}


void main(int argc, char **argv) 
{
    char    pszPath[MAX_PATH];
    HKEY    hkRoot = NULL, hkResult = NULL;
    DWORD   dwIdx = 0, dwRootIdx = 0;
    LONG    nResult = ERROR_SUCCESS;

    memset(pszPath, 0x0, MAX_PATH);

    // preflight our arguments  
    if (3 != argc)
    {
        DoUsage(argv[0]);
        return;
    }

    // first argument must be the root key name
    while (5 > dwIdx && 0 == dwRootIdx)
    {
        if (0 == strcmp(pszRootNames[dwIdx], argv[1]))
            dwRootIdx = dwIdx;
        else
            dwIdx++;
    }
    if (0 == dwRootIdx)
    {
        DoUsage(argv[0]);
        fprintf(stderr, "!!! no root key named %s\n", argv[1]);
        return; 
    }
            
    // get the path name
    strncpy(pszPath, argv[2], max(MAX_PATH, strlen(argv[2])));
    
    // open the corresponding key
    nResult = RegOpenKeyEx(hkRootList[dwRootIdx], pszPath, 0L, KEY_READ,
       &hkResult);
    if (ERROR_SUCCESS != nResult) 
    {
        fprintf(stderr, "Error %d while opening %s\n", nResult, pszPath);
        return;
    }
        
    // watch it until something happens or the program's terminated
    fprintf(stderr, "Watching %s\\%s...\n", pszRootNames[dwRootIdx], pszPath); 
    fflush(stderr);
    
    nResult = RegNotifyChangeKeyValue(hkResult, 
                                      true,         // tell us if subkeys change
                                      REG_NOTIFY_CHANGE_NAME + 
                                      REG_NOTIFY_CHANGE_ATTRIBUTES +
                                      REG_NOTIFY_CHANGE_LAST_SET + 
                                      REG_NOTIFY_CHANGE_SECURITY,
                                      NULL,         // don't pass an event
                                      false         // wait; don't be
                                                    // asynchronous
                                      );
    if (ERROR_SUCCESS != nResult) 
    {
        fprintf(stderr, "Error %d while monitoring %s\n", nResult, pszPath); 
        fflush(stderr);
        return;
    }
        
    // if we're still here, that means the key was changed
    time_t now = time((long *)NULL);
    fprintf(stderr, "!!! Key %s\\%s changed at %s", pszRootNames[dwRootIdx], 
            pszPath, ctime(&now));
    fflush(stderr);
}
8.3.1.2 Possible enhancements

WatchKey is a useful tool as it stands right now, but (as with almost every program ever written) it could be enhanced. Here are a few suggestions to get you thinking about how you could apply what you've learned:

  • The first, and most obvious, improvement would be to let users specify values for the bWatchSubtree and dwFilterOptions parameters, thus making the actual watching more flexible.

  • Instead of just printing out the date and time when a modification occurred, you can generate an Event Log entry.

  • Since RegNotifyChangeKeyValue can function asynchronously, you can modify the code in Example 8.4 so that it spawns a separate watcher thread for each key you want to monitor at one time. In conjunction with event logging, this provides a low-overhead auditing mechanism that can be applied only to keys you're interested in.

8.3.2 Example: A Stack-Based Wrapper Class

Earlier in the chapter, I alluded to a neat C++ feature that is sadly underutilized. Whenever you create an C++ object, its constructor is called. When you're done with the object and are ready to delete it, you call a disposal method that calls the object's destructor. Calls to these methods are supposed to balance so that you never construct anything that doesn't get destroyed, and you don't destroy any object more than once. This may sound suspiciously like the rule for Registry keys: open them, use them, and always close them.

If you create automatic objects on the stack, the compiler automatically calls the objects' destructors when it's time to destroy them. This may happen because your code has finished executing the scope where the objects are or because a jump or exception caused the objects to go out of scope. Here's a small example:

void test(void)
{
    anObject A;

    A.doSomething(  );
    if (A.IsEmpty(  ))
         throw(kRanOutOfData);
    A.DoSomethingElse(  );
    if (A.IsFull(  ))
         throw(kTooMuchData);
}

When this function starts up, A is allocated on the stack, and its constructor is called. The destructor may potentially be called in three cases: when the function returns normally, when kRanOutOfData is thrown, or when kTooMuchData is thrown. No matter how this function exits, A's destructor gets a chance to clean up whatever the constructor did.

Example 8.5 shows the class definition for a stack-based Registry key class. The constructor opens the key you specify, and the destructor closes it again. In between, there are members for getting and setting individual values.

Example 8.5. The StKey Class Definition
class StKey
{
    public:
        StKey(HKEY inRoot, LPCTSTR inPath, REGSAM inAccess = KEY_READ);
        ~StKey(  );
        
        LONG GetDWORDValue(LPCTSTR inValName, DWORD &outCount);
        LONG GetStringValue(LPCTSTR inValName, LPSTR outValue, DWORD &ioBufSize);
        LONG GetValueCount(DWORD &outCount);
        
        LONG SetDWORDValue(LPCTSTR inValName, const DWORD inVal);
        LONG SetStringValue(LPCTSTR inValName, LPCTSTR inVal, 
                             const DWORD inBufSize = 0, DWORD inType = REG_SZ);

        LONG AddDWORDValue(LPCTSTR inValName, const DWORD inVal);
        LONG AddStringValue(LPCTSTR inValName, LPCTSTR inVal,  
                             const DWORD inBufSize = 0, DWORD inType = REG_SZ);

    private:
        HKEY mCurrKey;  
};
8.3.2.1 How the code works

Example 8.6 shows the actual implementation of the StKey class.[7] The constructor and destructor are straightforward: they open and close the requested key, and that's it! Likewise, there's nothing magic about the GetValueCount, GetDWORDValue, or GetStringValue members.

[7] I omitted AddDWORDValue and AddStringValue from the example because they just call the corresponding Set routines.

The most interesting piece is actually the SetStringValue member. It handles more than one type of Registry string. You probably remember that values may contain plain strings (REG_SZ), expandable strings (REG_EXPAND_SZ), or multiple strings (REG_MULTI_SZ). SetStringValue correctly creates values of all three types; in addition, it's smart enough to figure out the correct string length based on the input type.

Example 8.6. The StKey Class Implementation
StKey::StKey(HKEY inRoot, LPCTSTR inPath, REGSAM inAccess /* = KEY_READ */)
{
	long nResult = 0;
	mCurrKey = NULL;
	nResult = RegOpenKeyEx(inRoot, inPath, 0L, inAccess, &mCurrKey);
	if (ERROR_SUCCESS != nResult)
		mCurrKey = NULL;
}


StKey::~StKey(  )
{
	if (mCurrKey)
	{
		RegCloseKey(mCurrKey);
		mCurrKey = NULL;
	}		
}
		

LONG StKey::GetValueCount(DWORD &outCount)
{
   return RegQueryInfoKey (mCurrKey, 
                              NULL, NULL, NULL,
                              NULL, NULL, NULL,
                              &outCount,
                              NULL, NULL, NULL, NULL);
}


LONG StKey::GetDWORDValue(LPCTSTR inValName, DWORD &outValue)
{
	DWORD sz = sizeof(DWORD);
	return RegQueryValue(mCurrKey, inValName, (LPTSTR)&outValue, (long *)&sz);
}


LONG StKey::GetStringValue(LPCTSTR inValName, LPTSTR outValue, DWORD &ioBufSize)
{
	DWORD sz = sizeof(DWORD);
	return RegQueryValue(mCurrKey, inValName, outValue, (long *)&ioBufSize);
}
		

LONG StKey::SetDWORDValue(LPCTSTR inValName, const DWORD inVal)
{
	return RegSetValueEx(mCurrKey, inValName, 0L, REG_DWORD, (BYTE *)&inVal,
               sizeof(DWORD));
}


LONG StKey::SetStringValue(LPCTSTR inValName, LPCTSTR inVal, 
             const DWORD inBufSize /* = 0*/,
             DWORD inType /*= REG_SZ*/)
{
	if (!IsValidStringType(inType))
		return ERROR_INVALID_PARAMETER;
	if (0 == inBufSize && REG_MULTI_SZ == inType)
		return ERROR_INVALID_PARAMETER;
	return RegSetValueEx(mCurrKey, inValName, 0L, inType, (BYTE *)inVal, 
						(inBufSize ? inBufSize : strlen(inVal)));
}


LONG StKey::AddDWORDValue(LPCTSTR inValName, const DWORD inVal)
{
	return SetDWORDValue(inValName, inVal);
}


LONG StKey::AddStringValue(LPCTSTR inValName, LPCTSTR inVal, 
             const DWORD inBufSize /* = 0*/,
             DWORD inType /*= REG_SZ*/)
{
	return SetStringValue(inValName, inVal, inBufSize, inType);
}
8.3.2.2 Possible enhancements

You could easily extend this class to support a Standard Template Library-style iterator capability for value. This makes it easy to iterate through all values of a subkey in a structured, exception-safe manner. You can also make the constructor smarter, perhaps by allowing it to recognize and parse a fully qualified path such as "\\enigma\HKLM\SOFTWARE\LJL\SMIME\Users" instead of requiring the root key and path to be separate. Another useful expansion is to make the class able to store values in USKs. For a real treat, consider building a stack-based class that loads and unloads hive files!

8.3.3 Example: Loading a Control with a Set of Values

If you store useful data as values attached to a subkey, at some point you'll want to get them out again. In writing an S/MIME-compliant electronic mail client, I found that I needed to get a list of stored user profiles (which lives in HKLM\SOFTWARE\LJL\SMIME\Users) and display them in a dropdown list so the user can efficiently pick a profile to use when logging in. The actual code that does so is in Example 8.7; it's fairly straightforward.

Example 8.7. Move the Values from a Key into a Windows Combo or List Box
#include <windows.h>

typedef enum {eCombo=0, eList} eBoxType;

HRESULT LoadBoxWithUsers(eBoxType inBoxType, HWND inControl, LPSTR inDefName, 
                         int &outSelected)
{
    DWORD   nResult = ERROR_SUCCESS;
    HKEY    hkFirstKey;
    HRESULT retVal = 0;
    long idx = 0;
    DWORD dwValCount = 0;
    
    SendMessage(inControl, (eCombo == 
                  inBoxType ? CB_RESETCONTENT : LB_RESETCONTENT),
                 (WPARAM)0, (LPARAM)0);
    outSelected = 0;

    // try to open HKLM\SOFTWARE\SMAIL; if we succeed, enumerate
    // through its subkeys and return the first one
    nResult = RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SOFTWARE\\LJL\\SMIME\\Users",
                            0L, KEY_READ, &hkFirstKey);
    if (ERROR_SUCCESS == nResult)
    {
        char *pszName = NULL;
        DWORD dwNameLen = 0;

        // find out what the longest subkey is and how many values exist
        nResult = RegQueryInfoKey (hkFirstKey, 
                              NULL, NULL,   // class & class size
                              NULL,         // reserved
                              NULL,         // # of subkeys
                              NULL,         // longest subkey length
                              NULL,         // class length
                              &dwValCount,  // # of values
                              NULL, NULL, 
                              &dwNameLen,   // longest value contents
                              NULL);
    
        // allocate buffers based on what we just learned
        pszName = (char *)malloc(dwNameLen);
        
        for (idx = 0; idx <= dwValCount; idx++)
        {   
            nResult = RegEnumValue(hkFirstKey, idx, pszName, &dwNameLen, NULL,
                              NULL, NULL, NULL);
            if (ERROR_NO_MORE_ITEMS != nResult)
                SendMessage(inControl, (eCombo == inBoxType ? CB_INSERTSTRING :
                            LB_INSERTSTRING), (WPARAM)-1, (LPARAM)pszName);

            // if this item matches the default, return it as a match
            if (inDefName && stricmp(pszName, inDefName) == 0)
                outSelected = idx;
            memset(pszName, 0x0, MAX_PATH); dwNameLen = MAX_PATH;
        }
        nResult = RegCloseKey(hkFirstKey);
        SendMessage(inControl, (eCombo == inBoxType ?CB_SETCURSEL :                                 
                               LB_SETCURSEL),(WPARAM)outSelected, (LPARAM)0); 
        free(pszName);
    }
    else
        retVal = E_NOT_FOUND;
    return retVal;
}

The first thing this code does is clear out the Windows list/combo box control; once that's done, it opens the key where the relevant values are stored. If RegOpenKeyEx succeeds, a call to RegQueryInfoKey returns the length of the longest value and the number of values attached to the key.

With that information in hand, it's easy to iterate through the values by repeatedly calling RegEnumValue. As each value is retrieved, it's added to the combo box. If the caller specifies a default value for the combo box, when that value is encountered, its index is saved so you can preset the combo box's selection. This makes it possible to remember the user's last selection and have it appear as the selection when the program's next run.

    Team LiB   Previous Section   Next Section