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 ChangesRegNotifyChangeKeyValue 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 worksAfter a check of its initial command-line arguments, the code performs the following steps:
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 enhancementsWatchKey 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:
8.3.2 Example: A Stack-Based Wrapper ClassEarlier 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 Definitionclass 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 worksExample 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.
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 ImplementationStKey::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 enhancementsYou 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 ValuesIf 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. |