8.1 The Registry APIThe original Registry API is defined in winreg.h, part of Microsoft's Win32 Software Development Kit (SDK) for NT 4.0 and Windows 95. The current version is still part of the Win32 API, but now it lives in the Microsoft Developer Network (MSDN) Platform SDK. There are 28 distinct routines in the Registry API, though most of them actually have two variants: one that works with standard one-byte ASCII strings and another that handles Unicode strings. The ASCII versions have routine names that end in "A," such as RegCreateKeyA, while the Unicode versions end with a "W," as in RegCreateKeyW. Macros in winreg.h automatically map the correct variant to the routine name. When you call RegCreateKey, you automatically get the correct Unicode or ASCII variant depending on how your header files are set up. (Of course, in Visual Basic or Perl this distinction is moot.) The Registry stores strings in Unicode format, so when you call one of the ASCII variants, the Registry code takes care of converting one encoding to another. As if this original set of functions wasn't enough, Microsoft has added a separate set of Registry-related API routines as part of the Internet Explorer 4.0/5.0 shell. These routines are delivered as part of the Shell Lightweight Utility API, and most of them are implemented in Version 4.71 and later of shlwapi.dll. All machines running Windows 2000 or 98 have this DLL (as of this writing, it's Version 5.00), while machines running Windows 95 or NT 4.0 have it if they also have Internet Explorer 4.0 or later. Some functions discussed later in the chapter are only available as part of Internet Explorer 5.0 or later; those functions are noted. 8.1.1 API Concepts and ConventionsIf you've used any other set of Win32 API routines, you'll probably find the Registry API easy to digest. If you haven't, though, a brief review of some Win32 API fundamentals will help flatten your learning curve. 8.1.1.1 Input and output parametersEach Registry routine described next has its own unique set of parameters. These parameters give you a way to tell the API routines what you want done and how to do it. It's important to make sure you specify the parameters completely and correctly. If you don't, you'll likely get ERROR_INVALID_PARAMETER back as an error; it's entirely possible that instead you might get a corrupted Registry and a crashed machine. In general, the C/C++ declarations for the Registry routines use pointers both for input and output. For example, strings are always passed as pointers (surprise!), as are outputs for things like security attributes and newly opened HKEYs. The Perl and Visual Basic declarations use the type system appropriate for the language, as you'll see in the sections that cover each language. 8.1.1.2 Registry error codesEvery Registry API routine returns an error code as its value. These codes, all of which are defined in winerror.h, give you an easy way to test for success or failure of an operation. Table 8.1 lists the most commonly used codes. A few routines can return other error codes as noted, but these are the ones you're most likely to see. Your code should always test for all returned errors (not just these) and handle them properly if they should occur. 8.1.1.3 Why some calls have names ending in "Ex"Back in ancient times,[1] the original Windows 3.x API was the One True API application developers were counseled to use. As programmers did use the API, the inertia of a large installed base made it hard for Microsoft to change the way any of the original 3.x routines worked. Instead of changing the originals, the Win32 API added new routines where necessary and gave them new names ending with Ex. For example, RegOpenKey begat RegOpenKeyEx, which adds an options flag and a SAM access context--both of which are specific to Win32.
In general, you should avoid using the original routines when an Ex equivalent exists. Most of the cool features of the Windows 2000 Registry (especially those related to security) aren't available with the "classic" API. In addition, it's possible that the old-style routines will stop being supported in future Windows versions. In a few cases it may make sense to use the old-style routine anyway; I've noted these exceptions where appropriate. 8.1.1.4 "Happy families are all alike"The whole point behind the Win32 API is that you can write programs that use a single API. As long as you stick with that API, your code should run on any Win32-compliant platform, whether it's Win95 on Intel, WinNT on Alpha, Windows 2000 on Itanium, or WinCE on whatever CPU the HPC builder chose. You're not supposed to have to care which underlying operating system is present. While this is a wonderful theory, it sometimes breaks down in practice. For example, many of the routines described here have slightly different behavior under Windows CE.[2] More importantly, some routines don't work at all under Win95.
This may be too harsh an indictment. What really happens is that the routines don't fail, but they don't do what they're supposed to; they just return ERROR_SUCCESS. This means that your code still executes under Win9x, but it may not do what you intended it to. At present, there are only four routines that behave this way under Win9x: RegRestoreKey, RegGetKeySecurity, RegSetKeySecurity, and RegNotifyChangeKeyValue. If your application uses any of these routines, be forewarned: you won't get back the data you expect when your code is run under Win9x. Be sure to handle these cases gracefully (for example, checking whether the SECURITY_DESCRIPTOR returned by RegGetKeySecurity is valid before trying to use it). The same is true for the shell APIs I mentioned earlier: none of those APIs are supported under Windows CE, and they may have slight functional differences between Windows 2000/NT and 95/98. 8.1.1.5 New and exciting datatypesOne of Windows 2000's biggest advantages over Win9x is its robust security architecture. Since the Win32 API is supposed to be common across Win9x, Windows 2000/NT, and Windows CE devices, you may have seen, and ignored, some of the Windows 2000-specific datatypes used in Registry API routines. These datatypes can be useful, so a quick introduction will help you get familiar with them. (Skip this section if you already know how to use these types.) The Registry API uses many standard Windows datatypes such as DWORD and LPSTR. However, there are six datatypes that are fairly unfamiliar to most programmers who haven't yet written Windows 2000-specific code. Each is used in at least one Registry call.
8.1.1.6 New routines = new datatypesWhen Microsoft added the shell utility routines as part of IE 4.0, they also had to create some new datatypes to fully support those routines. Most of the shell utility routines provide functionality not included in the standard Win32 API set. However, the file association routines (AssocCreate, AssocQueryKey, AssocQuery-String, and AssocQueryStringByKey) bundle several Registry operations into a single function. These routines actually encapsulate the IQueryAssociations COM object; its purpose is to return the correct key and OLE class information from HKCR for a specific type of document file. By providing a standard way to do this (instead of requiring every developer to roll their own) Microsoft is trying to reduce the number of association-related frustrations foisted on end users. The new datatypes are:
8.1.1.7 User-specific keysWindows NT 3.1 introduced the concept of multiple user profiles to the Windows world. The idea was that each user could have her own group of personal settings that would automatically be loaded when she logged on. In Windows NT 3.51, Microsoft expanded this concept to cover domains, so that users could get their personal setting (or profile) information no matter where in the domain they logged on. However, some applications store their settings under HKCU, and others use HKLM. Compounding the problem, not all programs and components keep their setting data in the Registry. The introduction of user-specific class keys (see Section 2.1.2.4) makes things even more complicated, since some per-user settings may actually be inherited from HKCR. To fix this problem, Microsoft has introduced the concept of user-specific keys (USK). The idea is that all settings for one user can be stored beneath that user's USK, which then conveniently becomes the user's profile, making the settings portable. Applications that use the shell utility API are encouraged to use the USK functions to store and retrieve user-specific data so that all the user's profile settings are stored in the same place. 8.1.1.8 An extremely brief exampleAlmost every C or C++ book includes an example based on the famous "Hello, World" example from Kernighan and Ritchie's The C Programming Language. Following that venerable tradition, Example 8.1 shows what a similar program that uses the Registry looks like. Example 8.1. A Modern Variation of the Canonical "Hello, World" Program#include <windows.h> #include <winreg.h> #include <stdio.h> // Hello, World! for the Registry: gets this machine's name and prints // it out. void main(void) { unsigned char pszName[MAX_PATH] = ""; DWORD nNameLen = MAX_PATH; HKEY hkResult, hStartKey = HKEY_LOCAL_MACHINE; long nResult = ERROR_SUCCESS; nResult = RegOpenKeyEx(hStartKey, "SYSTEM\\CurrentControlSet\\Control\\ComputerName\\ActiveComputerName", 0L, KEY_READ, &hkResult); if (ERROR_SUCCESS == nResult) { nResult = RegQueryValueEx(hkResult, "ComputerName", 0, 0, pszName, &nNameLen); if (ERROR_SUCCESS == nResult) printf("Hello, world, from %s!\n", pszName); else printf("I don't even know my own name.\n"); } RegCloseKey(hkResult); }
This example contains code to implement the three most basic--and most common--Registry operations:
Almost all programs that use the Registry involve these three steps. Of course, in addition to (or instead of ) reading Registry data, you can write new data to a value or enumerate a sequence of keys or values to find one that matches what you're looking for. You'll learn how to do all these things in the following sections.
8.1.2 Opening and Closing KeysIn Chapter 1, I pointed out the organizational similarities between a filesystem and the Registry. These similarities are more than skin deep: they extend to the actual process of moving data into and out of the Registry. In general, the same rules apply when working with Registry keys and their values as with disk files. First and foremost, you have to open a key when you want to use it, then close it when you're done. If you don't open a key, you can't get its values. If you don't close the key when done, other applications can't access it, and your changes aren't written out when you'd expect them to be. The API routines that open keys require two arguments: a path to the key you want to open and an open parent key. This may seem like a Catch-22: how can you open a key if you must already have an open key? The answer is simple: the root keys (HKLM, HKCC, HKCU, HKU, HKDD, and HKCR) are always open, so you can use them when you open any other key.
The next similarity involves access controls and rights. If you're accustomed to NTFS, Unix, or Novell filesystems, you know that files and directories can have permissions attached to them that govern who can open, modify, delete, and move things around. In ACLs, files also have rights, which the ACLs grant. One entry in the ACL might grant Administrator the right to read or write a file, while another might deny write access to members of the Domain Users group. Registry keys have these same controls and rights. As you'll learn in Chapter 9, you can keep your Registry secure by putting ACLs on security-sensitive keys. When you open a Registry key, you must specify what access you want to it: read, write, enumerate, and delete are all examples. Windows 2000 checks the access you request against the ACLs on the Registry key to decide whether or not you get access. The best way to stay out of trouble when opening and closing keys is to remember to balance key openings with closings. Later in the chapter (in Section 8.3.2), you'll see a C++ class, StKey, that automates the cleanup process. Please be sure to close any keys you open even when errors or exceptions interrupt the normal flow of control in your code. 8.1.2.1 Opening keysWhen you're ready to open a key, there are two different approaches you can take. The first one is to use the RegCreateKey or RegCreateKeyEx functions, which I'll talk about in a bit. They'll automatically open the key you specify or create it if it doesn't exist. The second method, which is probably better for most applications, is to open the key with RegOpenKeyEx or RegOpenKey. Why are these calls better? They fail when you try to open a key that doesn't exist, while the RegCreate functions will create a new key with no values in it. Imagine that you're calling a friend named Bill on the phone. If you call and are told "Bill's not here" by the person who answers, that's the equivalent of calling RegOpenKey routines on a nonexistent key. By contrast, calling Bill and being told "Bill's not here, but I'll pretend to be him" is more or less what happens when you call RegCreate. That may sometimes be desirable, but it's not a pleasant surprise if you're not expecting it. The recommended way to open a key is with RegOpenKeyEx. You supply an open key, which may be a root key or a key you've already opened; the name of the full path to the key you want to open; and a mask describing what access you want to the newly opened key. LONG RegOpenKeyEx(hKey, pszSubKey, dwOptions, samDesired, phkResult);
The following code opens a key under HKLM for reading, then goes on to do some other processing (which I've omitted here). If you combine the root key and the value of pszSubKey, you'll see that the key being opened is HKLM\SOFTWARE\LJL\ArmorMail\Users; if I'd already had any key in that path open (for example, HKLM\SOFTWARE\LJL) I could have shortened the subkey name accordingly. DWORD result = ERROR_SUCCESS; HKEY firstKey; // try to open the user list key; if we succeed, enumerate its subkeys result = RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SOFTWARE\\LJL\\ArmorMail\\ Users", 0L, KEY_READ, &firstKey); if (ERROR_SUCCESS == result) ... If you try to open a key for access that the DACL on the key doesn't allow (for example, trying to open any of the HKLM\HARDWARE subkeys for write access from an unprivileged user account), you get ERROR_ACCESS_DENIED for your trouble. One of the "strongly recommended" criteria for getting the Win9x and Windows 2000 certification labels is that you should open keys with the privileges you need: don't ask for KEY_ALL_ACCESS when what you really need is KEY_READ. You should ask for write access only when you're ready to write data to the Registry; this reduces the risk that your code will accidentally damage the Registry while you're developing it. If you're willing to use the default system security mask for key access, you can use the RegOpenKey function instead. It takes the same hKey, pszSubKey, and phkResult parameters as RegOpenKeyEx, but it doesn't accept a desired SAM mask. LONG RegOpenKey(hKey, pszSubKey, phkResult);
The only difference between RegOpenKey and RegOpenKeyEx is that the latter has two extra parameters. Apart from that, they function identically. One portability warning, though: as with the other Win 3.x Registry API calls, RegOpenKey is unsupported on Windows CE. If you're writing code you want to be portable, stick with the .Ex functions, tempting though the old ones may be. 8.1.2.2 Opening a key while impersonating another userAs it turns out, Windows NT and 2000 both cache the contents of HKCU for all threads in a process. This is a big efficiency win (which is why Microsoft did it), but if you're writing an application that uses multiple threads, it can pose a sticky problem if any of those threads has to impersonate another user. For example, let's say you're writing an antivirus utility. You want it to be able to scan memory and files owned by whichever users are present on the system, so you code it to spawn one thread for each interactive or network user. Guess what? The default behavior results in your application reading, and storing, settings only in HKCU, even if other users have set preferences in their own profiles. This problem is particularly acute for people who are writing management utilities that have to deal with users and services sharing a computer (or, worse, using Terminal Services). There's a way to fix this when writing applications for Windows 2000: the RegOpenCurrentUser call opens the appropriate user-specific key for the thread that calls it. For example, if you have one thread running as Administrator and another running as RA\paulr, and each thread calls RegOpenCurrentUser, one thread gets HKU\Administrator and one gets HKU\paulr.[4]
LONG RegOpenCurrentUser(rDesiredPerms, phkResult);
8.1.2.3 Opening the user's class dataIn Windows 2000, the class information that used to live only in HKCR has been partitioned into two chunks: one that lives in HKCR and one that occupies the new, user-specific HKCU\Classes subkey. When you want data about OLE/ActiveX objects or class definitions (say, to find out which class factory to use to create a new object), how do you know where to look? Worse still, what if you're writing a multiuser or server-based application that needs to get the correct settings for whatever user is currently making a request? Oh, the horror. The solution is a new, Windows 2000-only API call, RegOpenUserClassesRoot. This routine allows you to open a handle to the class data for a particular user. Windows 2000 automatically combines that user's HKCU\Classes key with the machine's HKCR data to present a single unified tree to your program. LONG RegOpenUserClassesRoot(hToken, dwOptions, samDesired, phkResult);
The dwOptions, samDesired, and phkResult parameters are all pretty straightforward, since they work the same as they do when calling RegOpenKeyEx. hToken takes a little more explaining: it's a process token like the one the system generates internally when you log on interactively. In fact, you can pass that same token to RegOpenUserClassesRoot, but normally you wouldn't need to, since you can get the active user's class data when running processes in that user's context. It's more likely that you'd need to get a token representing some user other than the current user. For example, in a multiuser server application, you'd probably want to retrieve each individual user's data by opening their class data key. There are six routines that can give you back a token of the type you need to call RegOpenUserClassesRoot: (see Table 8.7). Which of these routines you use will depends on what you're trying to do. Most of the time, though, you'll probably use either LogonUser, OpenProcessToken, or OpenThreadToken.
8.1.2.4 Closing keysThere's only one way to close a handle to a key: RegCloseKey. You pass in the HKEY you want to close. If it's successfully closed, you get ERROR_SUCCESS back. Otherwise, you get an error that indicates what went wrong. LONG RegCloseKey (hKey); You can actually call RegCloseKey on one of the predefined root key entries. It reports a successful close but doesn't actually close the root key. This frees you from worrying about whether the HKEY you're trying to close is really yours or not. When you close an HKEY, any data you've changed in that HKEY or its subkeys may be written to disk. On the other hand, it may not; the Registry support code may cache these changes until the next time it's convenient to flush them out to disk. Don't assume that your changes are immediately preserved as soon as you close a HKEY. Do assume that your changes are not preserved until you do so. 8.1.3 Creating KeysYou can create new keys anywhere you have permission. As I pointed out in earlier chapters, you probably won't need to do so very often unless you're writing applications that use the Registry to store their parameters. Just in case, though, here's how to do it. RegCreateKeyEx is the most powerful function for creating a new key. When you ask it to create a key, it does so, then opens it. If the key already exists, it just opens it and returns a handle to it. In either case, after a successful call to RegCreateKeyEx you'll have an open key handle that can be used for all manner of things as described elsewhere in the chapter. LONG RegCreateKeyEx(hKey, pszSubKey, Reserved, pszClass, dwOptions, samDesired, lpSecurityAttributes, phkResult, lpdwDisposition);
When you open an existing key, RegCreateKeyEx ignores the lpClass, dwOptions, and lpSecurityAttributes parameters, since their values are determined by the existing key. Once you successfully call RegCreateKeyEx, you're guaranteed to have an open HKEY you can use to add values or subkeys. Of course, a newly created key won't have any of either item, but an existing key that RegCreateKeyEx opened might indeed; be sure to check lpdwDisposition if you need to know whether the key was created or just opened.
You can also use the less-flexible RegCreateKey, but neither Microsoft nor I recommend it. It lacks a way to specify what access or security attributes you want to apply to the key, meaning that it may fail unexpectedly when trying to open an existing key that has an ACL applied to it. In addition, it doesn't tell you whether it created a key or opened it. LONG RegCreateKey(hKey, pszSubKey, phkResult);
8.1.4 Getting Information About KeysEvery key has a great deal of information associated with it, even if it's not immediately obvious. When you use one of the Registry editing tools, you see a neatly tree-structured view of what's beneath each root key, but the system maintains a lot more data beneath the surface so that it can efficiently access keys and values and give them back to requesting programs. RegQueryInfoKey gives you access to a total of 11 different pieces of data for any key in the Registry. Typically you use it to find how many subkeys or values exist so you can efficiently enumerate through them (more on that in the next section). RegQueryInfoKey looks like the following. LONG RegQueryInfoKey(hKey, pszClass, lpcbClass, lpReserved, lpcSubKeys, lpcbMaxSubKeyLen, lpcbMaxClassLen, lpcValues, lpcbMaxValueNameLen, lpcbMaxValueLen, lpcbSecurityDescriptor, lpftLastWriteTime);
Any of the parameters except hKey can be NULL; if you specify NULL for a parameter, that data isn't returned. Here's a small routine that gets the number of values attached to any open Registry key; notice that it passes NULL for everything except lpcValue : DWORD GetKeyValueCount(HKEY inKey) // Gets the count of values attached to a particular key. Returns // the value count (which may be 0) or -1 if an error occurs. { DWORD valCount = 0; DWORD result = ERROR_SUCCESS; result = RegQueryInfoKey (inKey, NULL, NULL, // class & class size NULL, // reserved NULL, // # of subkeys NULL, // subkey length NULL, // class length &valCount, // # of values NULL, NULL, NULL, NULL); if (ERROR_SUCCESS != result) valCount = -1; return valCount; } It's worth making special mention of lpcSubKeys, lpcValues, lpcbMaxValueNameLen, and lpcbMaxValueLen. It's often necessary to do some kind of processing over every key or value under a particular subkey. This enumeration is nothing more than an iterative loop that starts with the first key or value of interest, then proceeds on, continuing until it has processed every key or value. For example, you could enumerate the subkeys of HKU to find out the SIDs of every installed local account on a machine. Armed with that information, you can look up the account names to build a list of users who have profiles on the machine. These four parameters make it easier to efficiently enumerate keys and values. Knowing how many items there are makes it possible to enumerate any subset of a key's values, and knowing the maximum name and content lengths means you can allocate a buffer that's just the right size, instead of too big or too small, to hold the data returned by the enumeration. 8.1.5 Enumerating Keys and ValuesThe enumeration API routines treat a key's subkeys or values as an ordered list of n values, numbered from to n-1. You pass an index value to the API routines to indicate which key or value you want; the corresponding key or value is returned. For values, there's an extra wrinkle: keys can have a default value, which always appears as item in the enumeration list. (You'll see how this works in Section 8.1.5.3 later in this chapter.) This is convenient, but don't be misled: the values or keys aren't really an ordered list, and if you enumerate the same subkey twice in a row, you can potentially get items back in a different order each time. 8.1.5.1 Enumeration strategiesWhen you enumerate keys or values, there are a few different strategies you can use to process all the enumerated keys. The easiest way is to call RegQueryInfo-Key to find out how many subkeys or values exist, then use a simple loop to process every key or value. A small snippet implementing this tactic might look like: DWORD idx=0, keyCount = 0 LONG retVal = 0; retVal = RegQueryInfoKey (inKey, NULL, NULL, // class & class size NULL, // reserved &keyCount, // # of subkeys NULL, // subkey length NULL, // class length NULL, // # of values NULL, NULL, NULL, NULL); for (idx=0; idx < keyCount; idx++) { // get the idx'th key's name and length retVal = RegEnumKeyEx(interestingKey, idx, name, nameLen, NULL, NULL, NULL, NULL); // do something with it } This approach has the advantage of being simple to implement and understand. However, you may not want to process every key or value. Instead, if you want to process only keys or values that meet some criterion, you can use a conventional while loop like this: DWORD idx = 0; bool keepGoing = true; LONG retVal = 0; while (keepGoing) { retVal = RegEnumKeyEx(interestingKey, idx++, name, &nameLen, (unsigned long *)NULL, (char *)NULL, (unsigned long *)NULL, (LPFILETIME)NULL); if (ERROR_SUCCESS == retVal) { // If we're interested in this key, we'd process it further; // we might also set keepGoing here if we only want one key } keepGoing = (keepGoing && retVal == ERROR_SUCCESS); } With this approach, you don't have to know in advance how many keys or values exist, and it's a simple matter to stop enumerating as soon as you find what you're looking for. 8.1.5.2 Enumerating keysYou enumerate keys using the RegEnumKeyEx and RegEnumKey routines. They're very similar; the primary difference is that RegEnumKeyEx allows you to retrieve the modification time and class name for a subkey, while RegEnumKey doesn't. In either case, you simply supply the HKEY you want enumerated and an index value that indicates which subkey you want to see. The name (not the complete path) of the corresponding subkey is returned, so you can open any subkey you find by passing the name to RegOpenKey or RegOpenKeyEx. LONG RegEnumKeyEx(hKey, dwIndex, pszName, lpcbName, lpReserved, pszClass, lpcbClass, lpftLastWriteTime);
RegEnumKey is identical in function, except for having fewer parameters. LONG RegEnumKey(hKey, dwIndex, pszName, cbName); 8.1.5.3 Enumerating valuesOnce you've located a key of interest, you might want to enumerate its values. Most Registry keys have at least one value; quite a few have many values whose number and contents vary from machine to machine. (HKCR is a good example, because it differs depending on what classes and objects are registered on a machine.) You can accomplish this with RegEnumValue: [5]
LONG RegEnumValue(hKey, dwIndex, pszValueName, lpcbValueName, lpReserved, lpType, lpData, lpcbData);
To see RegEnumValue in action, check out Example 8.7 in Section 8.3.3 later in this chapter; the example illustrates the basic things you should do when enumerating a set of values:
8.1.6 Getting Registry DataMaybe you patiently enumerated a sequence or keys, or perhaps you already know just where the data you want is stored. Either way, at some point you'll want to actually retrieve a value stored under some Registry subkey. If you used RegEnumValue, you could have gotten the value's contents when you enumerated it, but if you just want to grab a single value whose path you know, there are better ways for doing so. 8.1.6.1 Getting a single valueThe first, and most useful, method of getting a single value's contents out of the Registry is the RegQueryValueEx function. As its name implies, it's a Win32 routine; you supply an open key and a value name, and it returns the value's datatype, length, and contents. LONG RegQueryValueEx(hKey, pszValueName, lpReserved, lpType, lpData, lpcbData);
The most straightforward way to call RegQueryValueEx is just to get the value, like this (assuming you're fetching a REG_DWORD value named "SomeValue" from a previously opened key): nResult = RegQueryValueEx(hOpenKey, "SomeValue", NULL, NULL, (LPBYTE)&theValue, &valSize); Since you always know how big a DWORD is, the size really isn't important. Things get a little more complex when querying for a string value named "SomeStringValue". At runtime, you don't know the string's length, which means you must either dynamically allocate a buffer or check to see whether there's more data available than your buffer can hold. RegQueryValueEx returns ERROR_MORE_DATA if the requested value has more data than can fit in the buffer length as specified by lpcbData: DWORD bufSize = MAX_PATH; char theBuf[MAX_PATH]; nResult = RegQueryValueEx(hOpenKey, "SomeStringValue", NULL, NULL, (LPBYTE)theBuf, &bufSize); if (ERROR_MORE_DATA == nResult) { // too much data for our buffer; fail, use another buffer, or do // something else } else if (ERROR_SUCCESS == nResult) { // continue normally } Alternatively, you can find out how big the value is, then allocate the buffer for it. This approach requires an extra Registry query but lets you economize on memory by not allocating any more than you actually need: DWORD bufSize = 0; char *theBuf = NULL; nResult = RegQueryValueEx(hOpenKey, "SomeStringValue", NULL, NULL, NULL, &bufSize); if (ERROR_SUCCESS == nResult) { theBuf = (char *)malloc(bufSize+1); // allow extra byte for NULL // terminator if (theBuf) { nResult = RegQueryValueEx(hOpenKey, "SomeStringValue", NULL, NULL, (LPBYTE)theBuf, &bufSize); if (ERROR_SUCCESS == nResult) // do whatever with the value free(theBuf); } } Notice that this code snippet adds an extra byte to the buffer to allow for the NULL terminator, which may be stored as part of the string. Also notice that extra space isn't allocated for a Unicode string: if you define UNICODE, the initial call returns the string's Unicode length in bufSize, but if UNICODE isn't defined, the string is converted into ANSI, and bufSize contains the ANSI string length.
You can also use RegQueryValue to request a key's value, but it can get only the default value (remember, that's the only value Win3.x supports, and RegQueryValue is a 3.x compatibility function). LONG RegQueryValue(hKey, pszSubKey, pszValue, lpcbValue );
8.1.6.2 Getting multiple valuesYou can retrieve multiple values from a key at once using RegQuery-MultipleValues, but its interface can be a little confusing. LONG RegQueryMultipleValues(hKey, valList, numVals, pszValueBuf, ldwTotalSize);
To use this function, fill out an array of VALENT structures: you put the value name you're looking for in ve_valuename, and RegQueryMultipleValues fills in the other fields for you: typedef struct value_entA { LPSTR ve_valuename; DWORD ve_valuelen; DWORD ve_valueptr; DWORD ve_type; }VALENTA, FAR *PVALENTA; On entry, pszValueBuf should point to a buffer big enough to hold all the value data you're requesting. On return, you can iterate through valList; each item's ve_valueptr member points to the location within pszValueBufwhere the value data's actually stored. You can also call RegQueryMultipleValues with an pszValueBuf of NULL; when you do, ldwTotalSize contains the buffer size required to hold all the requested values. 8.1.7 Adding and Modifying ValuesKeys can signify things based on their presence or absence, but values are the best way to store persistent data in the Registry. The RegSetValueEx function does double duty; it can create new values or change the contents of existing ones. LONG RegSetValueEx(hKey, pszValueName, Reserved, dwType, lpData, cbData);
If you call RegSetValueEx with the name of an existing value in pszValueName, its contents and type is replaced by whatever you pass in. If no such value exists, it's created with the contents and type you specify. In addition to RegSetValueEx, there's also a second value-setting function you may use: RegSetValue. It was originally part of the Win 3.1 Registry API. You may remember from Chapter 1 that the Win 3.1 Registry allowed only a single value for each key. In keeping with that heritage, RegSetValue allows you to set only the default value for a key, and the value you set must be a REG_SZ. I present this function for completeness, but you should avoid it in favor of RegSetValueEx. LONG RegSetValue(hKey,pszSubKey, dwType, pszData, cbData);
As with RegSetValueEx, if the key named in pszSubkey doesn't exist, it's created. In an additional twist, if the key named by pszSubkey doesn't exist, RegSetValue creates any keys necessary to construct a legal path, then adds the default value to it. Note that if all you want is to set the default value, you can do so using RegSetValueEx and passing NULL for pszValueName. Example 8.2 illustrates how RegSetValueEx works; the example sets the DiskSpaceThreshold value to the percentage of disk space you specify. This routine is used in a tool I wrote that configures new servers with the desired default settings before delivering them to customers or remote sites. Example 8.2. SetDiskWarningThreshold// This routine sets the DiskSpaceThreshold to the specified percentage. // You should check all the system's disk volumes to figure out a reasonable // percentage for the machine, then call this routine to set it. long SetDiskWarningThreshold(const int inThreshold) { char pszName[MAX_PATH] = "System\\CurrentControlSet\\Services\\LanmanServer\\Parameters"; HKEY hkResult = NULL; LONG nResult = ERROR_SUCCESS; // preflight our arguments if (inThreshold < 1 || inThreshold > 99) return ERROR_INVALID_PARAMETER; // open the key with write access so we can set the value nResult = RegOpenKeyEx(HKEY_LOCAL_MACHINE, pszName, 0L, KEY_WRITE, &hkResult); if (ERROR_SUCCESS == nResult) { nResult = RegSetValueEx(hkResult, "DiskSpaceThreshold", 0L, REG_DWORD, (unsigned char *)&inThreshold, sizeof(int)); if (ERROR_SUCCESS == nResult) nResult = RegCloseKey(hkResult); } return nResult; } 8.1.8 Deleting Keys and ValuesYou may find it necessary to delete keys or values from within your home-brewed Registry utilities. Since many of the lesser-known features of Windows 2000 and NT discussed in Chapter 10, function based on the presence or absence of special trigger keys, turning these features on or off may require you to delete values, and there's no way to do so with a .REG file. You must be careful with your newfound destructive powers, though; accidentally deleting the wrong key or value can make your system stop working altogether. Before you delete a key or value, you must have the parent key opened with adequate access. If you supply KEY_WRITE as the REGSAM value when you open the key, you can delete it. You can also request KEY_CREATE_SUB_KEY or KEY_SET_VALUE rights to gain delete access to keys and values, respectively. There's one other thing worth mentioning here: when you delete a key or a value, it's not actually deleted. Instead, the Registry subsystem marks the deleted items as deleted, but doesn't delete them until the next time Registry data is flushed (either explicitly with RegFlushKey or automatically by the kernel's lazy flusher). If you try to read, write, or enumerate a key or value that's been marked as deleted, you get ERROR_KEY_DELETED as a return value. You can always call RegCloseKey on a deleted key without getting an error, though. 8.1.8.1 Deleting a keyYou delete individual keys with the RegDeleteKey routine. If you specify a valid key and subkey, the key is immediately marked for deletion, even if other processes currently have the key open. This is different from the file metaphor used elsewhere in the Registry; if you try to delete an open file, the delete operation will fail, but not so with RegDeleteKey. At that point, attempts by other processes to access data attached to the open key will fail. LONG RegDeleteKey(hKey, pszSubKey);
You can't delete a root key, and you can't delete first-level subkeys of root keys. For example, you can't remove HKLM\SOFTWARE or HKCU\SOFTWARE, but you can remove HKLM\SOFTWARE\Microsoft (though I wouldn't recommend it). In addition, you may not delete a key that has subkeys; if you try, you get an error. It's okay to delete keys that have values; the values are deleted along with the key.
8.1.8.2 Deleting a valueDeleting values is wonderfully straightforward (as long as you have KEY_WRITE or KEY_SET_VALUE access on the target key)! RegDeleteValue removes the specified value from the key you provide. LONG RegDeleteValue(hKey, pszValueName);
If pszValueName is NULL or contains an empty string, RegDeleteValue deletes the default value (you know, the one that appears as <Default> or No Name in Registry editors). Otherwise, pszValueName must contain the correct name of an existing value. 8.1.9 Using Registry Security InformationUnder Windows 2000, every object in the entire system has security information attached to it. Registry keys are just objects, so they too can have ACLs that control who can read, write, or delete the key and its values. Ordinarily, you don't need to control these ACLs; when you do, RegEdt32 is probably the best tool for doing so. If you find it necessary or desirable to get a key's security data programmatically, though, you certainly can. ACLs come in two types: system ACLs, or SACLs, are owned by (and can only be changed by) the system; while discretionary ACLs (DACLs for short) are controlled by the owner of the object. As you might expect from security information, not just anyone can read either type of ACL. To read the DACL, the requesting process must have READ_CONTROL access on the key. To get this access, the requester must either own the key itself, or the DACL must grant READ_CONTROL to the account under which the requester is running. System ACLs are trickier. They can be read only by applications that have been granted the ACCESS_SYSTEM_SECURITY permission. In turn, the only way to get ACCESS_SYSTEM_SECURITY is for the calling process to ask for the SE_SECURITY_NAME privilege, open the key with a REGSAM value of ACCESS_SYSTEM_SECURITY, then turn off SE_SECURITY_NAME again. To actually retrieve a key's security data (assuming you've fulfilled the access control requirements), you can use RegGetKeySecurity . Besides passing in the name of the key whose information you want, you must also fill in the SecurityInformation field to indicate which data you want. If you have permission, pSecurityDescriptor is filled with the ACL or ownership data on return, and lpcbSecurityDescriptor contains the ACL size. ACLs vary in size, since they may contain one or many entries. LONG RegGetKeySecurity (hKey, SecurityInformation, pSecurityDescriptor, lpcbSecurityDescriptor);
If the buffer pointed to by pSecurityDescriptor is too small, RegGetKey-Security returns ERROR_INSUFFICIENT_BUFFER and the lpcbSecurity-Descriptor parameter contains the number of bytes required for the requested security descriptor. This makes it possible to efficiently allocate a buffer of the right size by calling it twice, like this: long retVal = 0, aclSize = 0; PSECURITY_DESCRIPTOR pWhat = NULL; retVal = RegGetKeySecurity(theKey, DACL_SECURITY_INFORMATION, pWhat, &aclSize); if (ERROR_INSUFFICIENT_BUFFER != retVal) throw(retVal); pWhat = malloc(aclSize); retVal = RegGetKeySecurity(theKey, DACL_SECURITY_INFORMATION, pWhat, &aclSize); if (ERROR_SUCCESS != retVal) throw(retVal); 8.1.9.1 Setting an item's security information
Once you've gotten a security descriptor and modified it,[6] you can write it back to the key that owns it with RegSetKeySecurity.
LONG RegSetKeySecurity (hKey, SecurityInformation, pSecurityDescriptor);
To ensure that your new security data gets written, you should call RegCloseKey on the modified key after successfully calling RegSetKeySecurity. This is true even if you've set security on one of the root keys; it won't actually be closed, but its cached security data is updated. 8.1.10 Connecting to Remote ComputersIn Chapter 4, and Chapter 5, you learned how to use RegEdit and RegEdt32 to edit Registry data on remote computers. Adding this same functionality to your own programs is trivial: all you need do is call RegConnectRegistry and use the HKEY it returns in any other calls you make to Registry API functions. When you're finished with the remote key, you call RegCloseKey on it as though it were a local key. The API function declaration looks like the following. LONG RegConnectRegistry(pszMachineName, hKey, phkResult);
HasPackage (shown in Example 8.3) showcases RegConnectRegistry in action. You supply it with a machine name and a subkey; it checks the Registry on the specified machine to see whether it has a subkey of HKLM\SOFTWARE by the name you specify. The call to RegConnectRegistry and the corresponding RegCloseKey on the key it returns are the only changes needed to enable remote Registry connections in this small program. Example 8.3. HasPackagevoid main(int argc, char **argv) { char pszName[MAX_PATH]; HKEY hkRemoteKey = NULL, hkResult = NULL; DWORD dwIdx = 0; LONG nResult = ERROR_SUCCESS; memset(pszName, 0x0, MAX_PATH); // preflight our arguments if (argc < 3) DoUsage(argv[0]); nResult = RegConnectRegistry(argv[1], HKEY_LOCAL_MACHINE, &hkRemoteKey); if (ERROR_SUCCESS == nResult) { sprintf(pszName, "SOFTWARE\\%s", argv[2]); nResult = RegOpenKeyEx(hkRemoteKey, pszName, 0L, KEY_READ, &hkResult); if (ERROR_SUCCESS == nResult) { fprintf(stdout, "%s has a key for %s.\n", argv[1], argv[2]); } else { fprintf(stderr, "Error %d while opening SOFTWARE\\%s on remote machine %s\n", argv[2], argv[1]); } nResult = RegCloseKey(hkResult); nResult = RegCloseKey(hkRemoteKey); } else { fprintf(stderr, "Error %d while opening remote registry on %s\n", nResult, argv[1]); } fflush(stdout); } 8.1.11 Moving Keys to and from HivesIn Chapter 3, Chapter 4, and Chapter 5, you learned how to use the Registry editor functions that allow keys and values to be saved into hive files and later restored. You can do the same thing with your own code by using the routines discussed in this section. 8.1.11.1 Saving keysThe first step in moving keys in and out of hives around is to create a hive; you can do this with RegSaveKey . LONG RegSaveKey(hKey, pszFile, lpSecurityAttributes);
If the file you specify in pszFile already exists, RegSaveKey will fail with the ERROR_ALREADY_EXISTS error code. This prevents you from accidentally overwriting another hive file you previously saved. There's another subtlety involved with pszFile : if you don't specify a full path, the file is created in the process's current directory if the key is from the local Registry, or %systemroot%\system32 for a key on a remote machine. The created file has the archive attribute set and whatever permissions are specified by lpSecurityAttributes. Instead of creating a brand-new security descriptor, you may pass NULL to have whatever security context applies to the process applied to the file. 8.1.11.2 Loading keysOnce you've saved keys into a hive file, the next thing you're likely to want to do is load them. You can do so in two distinct ways: you can load a hive as a new key, or you can replace the contents of an existing key with the hive's contents. Either approach requires the process that loads the keys to have the SE_RESTORE_NAME privilege. RegLoadKey supports the former: you tell it what file to load and what to name the new subkey, and it creates the specified subkey and loads the file into it. RegLoadKey will fail if the file doesn't exist or if the named subkey does exist. LONG RegLoadKey(hKey, pszSubKey, pszFile);
If you want to overwrite an existing key that's part of one of the standard hives, you can instead call RegRestoreKey. Like RegLoadKey, it takes a parent key and the name of a file to load. However, in this case the parent key's subkeys are replaced by the contents of the file. For example, if you open HKLM\SOFTWARE\Microsoft\Windows and pass that to RegRestoreKey, the key with that name persists, but all subkeys and values beneath it are deleted. After RegRestoreKey returns, the victim key contains whatever values and subkeys were in the loaded file. LONG RegRestoreKey(hKey, pszFile, dwFlags);
8.1.11.3 Replacing a loaded keyOnce you've loaded a hive file with RegLoadKey, you can replace the loaded key with another hive file. This is a good way to dynamically swap between several hives' worth of data. However, changes don't take effect until the machine is restarted. LONG RegReplaceKey(hKey, pszSubKey, pszNewFile, pszOldFile);
8.1.11.4 Unloading a keyRegLoadKey allows you to load a stored hive file as a new hive under HKLM or HKU. Once you've loaded a hive, it makes sense to have a way to unload it when you're done, and RegUnloadKey provides that functionality. LONG RegUnLoadKey(hKey, pszSubKey);
You can unload only keys you load yourself, which prevents unloading (accidentally or on purpose) an important key such as HKLM\SOFTWARE. The process that calls RegUnloadKey must have the special SE_RESTORE_NAME privilege. 8.1.12 Getting Notification When Something ChangesIf you want to write a program that does something when a particular Registry key or value changes, you can do so by sitting in an infinite loop and periodically checking the item of interest to see whether it changes. This is terribly inefficient, though, so it's good that there's another way to do it. The RegNotify-ChangeKeyValue routine allows you to register your interest in a particular key or value, then go do something else. Your code gets a notification you when the Registry key (or its attributes) changes; it doesn't, however, tell you if the key is deleted. LONG RegNotifyChangeKeyValue (hKey, bWatchSubtree, dwNotifyFilter,hEvent fAsynchronous); 8.1.13 Flushing Registry ChangesThe Registry uses a "lazy flusher" to propagate changes from memory to disk. The overall goal is to minimize the number of disk I/O operations, since they tend to be relatively time-consuming. The lazy flusher achieves this goal by not immediately writing every change out to disk as it occurs. Instead, it aggregates changes and writes them when the system is mostly idle. When you call RegCloseKey, whatever changes you've made are thus not immediately copied to disk. There can be an interlude of indeterminate length (Microsoft says "as long as several seconds" without elaborating) before your data actually hits the disk. For most applications, this is perfectly acceptable. However, if for some reason you want to make sure your changes get written to disk, you can use the RegFlushKey routine to immediately force a Registry update: LONG RegFlushKey (hKey); Calling this routine forces the lazy flusher to buckle down and flush the specified key's data to its hive file; it may also cause other keys to be written as well. Flushing the cached data also updates the .LOG files that act as a backup copy of the Registry. The Win32 SDK warns that this function is expensive, so you shouldn't call it often. RegFlushKey returns ERROR_SUCCESS when all goes well or a standard Win32 error code for failed flush attempts. |