8.2 The Shell Utility API RoutinesThe shell utility API routines allow you to use a different set of API routines instead of the ones I've discussed up until now. While this might seem like a needless fragmentation of what should be a unified API, there's are two reasons for it. First, some of the tasks that the shell utility API can do don't have direct equivalents in the Win32 API. For example, the shell API has routines to copy a key and its contents, delete an entire key and all its subkeys, and so on. Second, Microsoft is clearly trying to position the utility API as the thing you use if you're writing small utility components- not all of which will necessarily run on Windows as we know it today. (For example, what about an embedded version of Windows running on a smart card?)
8.2.1 Working with File AssociationsOne of the biggest innovations of the Macintosh when it was originally introduced was the idea that documents would somehow "know" what application they belonged to. The Mac OS implemented this by storing eight bytes of type and creator information with each file. The type bytes identified the file's datatype, and the creator bytes associated the file with a particular application. Windows 3.0 introduced the concept of file associations to the PC world, but the Windows version was based on binding a three-character DOS filename extension to an application. Later versions of Windows expanded this mapping so that object types (like, say, an Excel table) could be mapped to an executable; in addition, MIME types could be associated with file extensions too. Managing these associations has long been painful for programmers. In particular, it's difficult to find what application or DLL the user wants called to create or modify some existing object type, even more so under Windows 2000. Recall that Windows 2000 stores class information in two places: HKCR and HKCU\Classes. In an effort to ease this pain, Microsoft created a Component Object Model (COM) interface called IQueryAssociations. By making the appropriate COM calls, you can get a pointer to the IQueryAssociations interface, call some of its routines, and get the desired information. However, not everyone is comfortable using COM, so Microsoft added a set of wrapper routines in the shell utility API. These handle all the messy COM calls while providing a simple C interface to their functionality. 8.2.1.1 Getting a file association key from the RegistryOne common operation is to look up the file association for some piece of data you have, like a file extension or a CLSID. There are three ways to do so, depending on what data you have and what kind of data you want back. If you want to get an HKEY that points to the key where the association is stored, you can do so with the AssocQueryKey routine. HRESULT AssocQueryKey (afFlags, akKey, pszAssoc, pszExtra, HKEY *phkResult)
In large part, the result you get back is determined by the value you pass in the pszAssoc parameter. You can pass in a file extension (.cpp), a CLSID (using its GUID, in the standard "{GUID}" format), a ProgID (e.g., "Excel.Worksheet.2"), or the name of an .exe file (but only if the ASSOCF_OPEN_BYEXENAME flag is set). If you'd rather have a text string describing the association, use AssocQuery-String instead; it's similar to AssocQueryKey. HRESULT AssocQueryString (afFlags, asStr, pszAssoc, pszExtra, pszOut, pcchOut)
The permissible values for pszOut are the same for this function as for AssocQueryKey. The third way is a hybrid of the first two. When you call AssocQueryString-ByKey, you pass in the key where you want the routine to start looking, along with a mix of the parameters used for AssocQueryString and AssocQueryKey. Here's what it looks like. HRESULT AssocQueryStringByKey (afFlags, asStr, hkAssoc, pszExtra, pszOut, pcchOut)
8.2.1.2 Getting a pointer to the IQueryAssociations interfaceIf you need to call one of the routines in the IQueryAssociations class that doesn't have a wrapper, you can do so by getting a pointer to an object that contains that interface and using the pointer to call the routine you want. While a discussion of writing this sort of COM code is outside the scope of this book, the shell utility API routine provides an easy way to get a pointer to the interface so you can use it: AssocCreate. Be aware that this routine doesn't actually create anything you can use directly; all it does is return a pointer to a COM interface, creating an object to handle that interface if necessary. HRESULT AssocCreate (clsID, refIID, pInterface)
8.2.2 Copying and Deleting Keys and ValuesThe original Registry API had a few weak areas. One was the annoying lack of any functions for copying keys and values from one place to another, as you might do when establishing initial settings for a new installation. The API routines for saving and restoring keys provided a somewhat clunky way to do this, but the shell utility API provides a cleaner way. SHCopyKey (available with Version 5.0 or later of SHLWAPI.DLL) recursively copies all the subkeys and values beneath the source key you specify to the destination key. LONG SHCopyKey (hkSrcKey, szSrcSubkey, hkDestKey, fReserved)
Deleting keys in Win32 code has always been a little tricky. The semantic behavior of RegDeleteKey varies according to which operating system it runs on. In the Windows 9x family, it deletes the key whether or not it's empty, while under Windows 2000/NT, the function fails if the target key has any subkeys. In an effort to keep from surprising people with unexpected behavior, Microsoft added two separate routines to clearly distinguish two different operations: removing a key that you know (or suspect) to be empty and removing a key when you don't care if it's empty or not. SHDeleteEmptyKey is what you use in the first case. It fails if the specified target key has any subkeys, although (like RegDeleteKey) it happily removes any values that are attached to the otherwise-empty target key. HKEY SHDeleteEmptyKey (hkKey, pszTargetSubkey);
If you really want to remove the key and all its subkeys, you should use SHDeleteKey instead. It recursively removes the target and all its subkeys, along with all values attached to the target or any of its descendants. HKEY SHDeleteKey (hkKey, pszTargetSubkey);
There's also a new routine intended for removing individual values: SHDeleteValue accepts the name of a subkey and value, then removes the value from that key. Unlike RegDeleteValue, which requires you to pass in an HKEY that points to the target value's parent key, SHDeleteValue accepts any open key as long as it's a parent of the target, plus the path from that key to the target's actual home. HKEY SHDeleteValue (hkKey, pszParentSubkey, pszTargetValue);
8.2.3 Getting Key and Value InformationWhen you're ready to read and write individual keys and values, you'll probably find it helpful to get some fundamental information about the keys and values themselves. 8.2.3.1 Querying keys and valuesRegQueryInfoKey gives you a wealth of information about a key--maybe too much for some uses. Most of the time, you need to know three things about a key: how many subkeys it has, how many values it has, and how much space to allocate when fetching things from it. SHQueryInfoKey provides most of this data. LONG SHQueryInfoKey(hKey, pdwSubKeys, pdwMaxSubKeyLen, pdwValueCount, pdwMaxValueNameLen);
For example, you can use it to find how many subkeys were beneath a particular key so you can efficiently enumerate them. One thing you can't do with it, though, is find out the length of the longest value under a key, as you can with RegQueryInfoKey. That means that if you want to preallocate a buffer to the size of the largest value under a key, you can't do it efficiently unless you use RegQueryInfoKey. SHQueryValueEx is identical to RegQueryValueEx, down to the inclusion of the "reserved" parameter. That being so, I'm not going to cover it again here, but it exists if you want to use it. 8.2.3.2 Getting and setting valuesLet's start with something familiar: SHQueryValueEx is a dead ringer for RegQueryValueEx. It takes the same parameters and has the same restrictions and conditions, so it's pretty much a drop-in replacement. LONG SHQueryValueEx(hKey, pszValueName, pdwReserved, pdwType, pvData, pcbData);
SHGetValue is a new routine. If you think of the fairly useless (and now deprecated) RegGetValue call, don't: SHGetValue is much more interesting, since it essentially duplicates RegQueryValueEx. Instead of passing the bogus "reserved" parameter, SHGetValue expects you to pass in a path that points to the key that owns the value you're trying to query. LONG SHGetValue(hKey, pszSubKeyName, pszValueName, pdwType, pbData, pcbData);
When you want to set a value, you can use RegSetValueEx or its very close relative, SHSetValue. As with many of the other SH* routines and their vanilla Win32 counterparts, the primary difference between these two is that SHSetValue lets you specify the full path to the target key, instead of requiring you to open the target key and pass in a handle to it. LONG SHSetValue(hKey, pszSubKeyName, pszValueName, pdwType, pbData, pcbData);
8.2.4 Enumerating Keys and ValuesThe Registry API already contains a number of routines that enumerate keys and values. Guess what? The shell utility API contains more of them, including several normally used with user-specific values. In particular, there are two routines that duplicate existing functionality in the Registry API: SHEnumKeyEx and SHEnumValue. Why? Mostly because Microsoft wanted to provide a more straightforward set of routines without breaking applications that depended on the original API. Let's look at SHEnumKeyEx first. LONG SHEnumKeyEx (hKey, dwIndex, pszName, pcchName)
Like RegEnumKeyEx, SHEnumKeyEx allows you to iterate through every key under the specified subkey. Unlike its big brother, SHEnumKeyEx doesn't allow you to get the key's modification time. You can still use RegQueryInfoKey to determine in advance what indices to use, or you can start with zero and work your way up. SHEnumValue is a somewhat redundant beast. It looks like RegEnumValue, except that SHEnumValuedoesn't have the useless "reserved" parameter. Other than that, the two are functionally identical. LONG SHEnumValue (hKey, dwIndex, pszValueName, pcchValueName, pdwType, pvData, pcbData)
8.2.5 Working with User-Specific KeysIf you decide to use the shell API routines that give you access to user-specific keys (USKs), you'll find that they're familiar and different at the same time. Most of the things you have to do when using the standard Registry API are still required; in particular, you still have to open and close keys before you read or write them or their values, and the normal Win32 access controls still apply. 8.2.5.1 Creating and removing keysBefore you can do much of anything else with user-specific keys, you need to be able to create them. Of course, the counterpart of creating something is deleting it, so it's handy to know how to remove USKs as well. You create a new USK with SHRegCreateUSKey, which also opens the key after creating it. LONG SHRegCreateUSKey(pszPath, samDesired, hkRelUSKey, phkNewUSKey, dwFlags);
The way this routine works requires some explanation. Since USKs can be created either under HKCU or HKLM, you have to use the dwFlags parameter to specify where you want the key created. Note that SHRegCreateUSKey creates the key and opens it. If the key already exists, you get to choose whether you want to open it in its existing location or force creation of a key with the same name under another root. When you request that a key be created under HKCU, though, you may find that it's actually created under the user's subkey of HKU. Once you're done using a key (usually as part of your program's uninstallation code), it's polite to delete any USKs you've created. You do so with SHRegDeleteEmptyUSKey, which does just about what you'd expect. LONG SHRegDeleteEmptyUSKey(hkUSKey, pszTargetPath, delRegFlags);
Note that SHRegDeleteEmptyUSKey refuses to delete a USK unless all its subkeys and values have been previously removed; this prevents you from accidentally removing settings you want to keep. In practice, that means you need to enumerate through any USKs you create and remove their contents before removing them at uninstall time. 8.2.5.2 Opening and closing keysYou have to open and close USKs just as you do regular keys. Since there's a new opaque type for USKs (HUSKEY), you can't intermix the shell utility routines and the ordinary Win32 API routines. To open a USK, use SHRegOpenUSKey. LONG SHRegOpenUSKey(pszPath, samDesired, hkRelUSKey, phkNewUSKey, fIgnoreHKCU);
Once you have the key returned in phkNewUSKey, you can pass it to any of the other USK-related functions covered in this section. When you're done, of course, you need to close the key by calling SHRegCloseUSKey. LONG SHRegCloseUSKey(hkTarget);
8.2.5.3 Getting key and value informationAfter you've opened a USK, you'll probably need to get information about the keys and values in it. There are a total of three routines that do so: SHRegQueryInfoUSKey, SHRegEnumUSKey, and SHRegEnumUSValue. These are similar to their non-USK counterparts. For example, SHRegQueryInfoUSKey takes the same parameters as SHQueryInfoKey and returns the same information: a count of how many values and subkeys the specified USK has, plus the length of the longest subkey and value names. There are a few differences, though. When you want to enumerate the subkeys of a USK, SHRegEnumUSKey is the appropriate routine to use. Like all the other enumeration routines I've talked about in this chapter, SHRegEnumUSKey allows you to walk an entire USK and get the names of each of its subkeys. Unlike (say) SHRegEnumKeyEx, though, the USK version needs another parameter: a flag that indicates whether to return information about the USK under HKLM or HKCU. LONG SHRegEnumUSKey(hUSKey, dwIndex, pszName, pcchName, enumRegFlags);
SHRegEnumUSValue is just like SHEnumValue, with one exception: it adds an enumRegFlags parameter that accepts the same values as the one defined for SHRegEnumUSKey. 8.2.5.4 Reading valuesWhen you want to read or write values under a USK, you'll again find that the process is quite similar to what you're accustomed to doing for ordinary Registry data. Once you have an open USK, you can get a specific value from it in two ways. SHRegGetUSValue is the general-purpose routine for fetching USK values. You pass in the value's name and get back its contents, just like SHGetValue or RegQueryValueEx. However, since SHRegGetUSValue is for USKs, its argument list looks more like the other USK routines discussed thus far. LONG SHRegGetUSValue(pszSubKey, pszValue, pdwType, pvData, pcbData, fIgnoreHKCU, pvDefaultData, dwDefaultDataSize);
You may have noticed that there's no hUSKey parameter for this routine. That's because you don't have to open a USK to get a value with SHRegGetUSValue; it opens and closes the key for you. This is easy, but inefficient if you need to fetch several values in a row. In that case, you probably want to use SHRegQueryUSValue instead by opening the desired USK, calling it several times, and closing the key. LONG SHRegQueryUSValue(hUSKey, pszValue, pdwType, pvData, pcbData, fIgnoreHKCU, pvDefaultData, dwDefaultDataSize);
There's also a special-purpose routine that gets a single value of type BOOL from a USK. SHRegGetBoolUSValue fetches a single Boolean value, using the same method as SHRegGetUSValue. It opens the specified key, retrieves the value, and returns it to you. This is an easy function to use, since its functionality is so limited, but it's still handy if you want to check the value of a Boolean flag in a USK within your program. BOOL SHRegGetBoolUSValue (pszSubkey, pszValue, fIgnoreHKCU, fDefault)
For example, let's say you write an application that allows users to download the contents of an Outlook contacts folder to a GSM mobile phone. Most GSM phones can store numbers on a smart card in the phone or in the phone's internal RAM, so it would be nice if you let the user specify a default for where new numbers should go. At download time, it's fairly easy to check the setting before blasting data out to the phone. // get all the contact info from Outlook // see where the user wants the numbers stored BOOL storeInPhone = SHRegGetBoolUSValue("Software\\RA\\PhoneBlaster", "StoreNumbersInPhone", false, false); if (storeInPhone) // store the numbers in the phone else // store the numbers on the SIM card 8.2.5.5 Writing and deleting valuesSo far, I've talked only about reading values. Fortunately, there are routines you can use to store values in USKs too. In fact, there are two distinct ways to write a value. SHRegSetUSValue just sets the value without requiring you to open and close the USK you're using, while SHRegWriteUSValue expects to have an open USK passed to it. Like the functions you use to read values, the one you use depends on whether you're doing one operation (in which case SHRegSet-USValue is easier to use) or several (in which case SHRegWriteUSValue is more efficient). LONG SHRegSetUSValue(pszSubKey, pszValue, dwType, pvData, cbData, dwFlags);
SHRegWriteUSValue looks pretty much the same, except that it requires a handle to an open USK as once of its parameters: LONG SHRegWriteUSValue(hkUSKey, pszValue, dwType, pvData, cbData, dwFlags);
Finally, you can delete a single value from a USK when you're done with it. SHRegDeleteUSValue does the trick (remember, you have to remove the values from a USK before you can remove it with SHRegDeleteEmptyUSKey). Removing a value is simple: you have to pass an open USK, the name of the value, and a flag word that indicates where you want to remove the value if found. LONG SHRegDeleteUSValue(hkUSKey, pszValue, delRegFlags);
8.2.6 LeftoversThere are three routines that don't really belong anywhere else. Two of them allow you to read or change file paths, while the third duplicates a key handle. Let's look at it first; it's probably the simplest routine in the entire Registry API set. HKEY SHRegDuplicateHKey (hkKey);
SHRegDuplicateHKey does only one thing, but it does it well: it duplicates a current HKEY and makes a copy of it. If you're accustomed to the dup( ) function in Unix, this does exactly the same thing, so you can quickly duplicate a key handle (whether open or closed) and go on to do separate things to the two handles, independent of one another. The path functions are also pretty straightforward. Windows 2000 really embodies Microsoft's philosophy that users should keep their data in their profiles, and that certain areas of the filesystem should be used only by applications and the OS. Accordingly, these functions allow you to pass in a path and have any symbolic names in it expanded to match actual folders on disk, or vice versa. The symbolic names that these functions support are shown in Table 8.8.
The first of these routines, SHRegGetPath, takes the path to a Registry key that contains a file path. If that value is a REG_EXPAND_SZ, SHRegGetPath expands any symbolic names found in the path; if not, it returns the string without modifying it. LONG SHRegGetPath (hKey, pszTargetSubkey, pszValue, pszExpandedPath, dwFlags)
The counterpart of SHRegGetPath is SHRegSetPath, which takes a path string that contains one or more full path to the folders listed in the rightmost column of Table 8.8, then converts those paths to the symbols listed in the table. LONG SHRegSetPath (hKey, pszTargetSubkey, pszValue, pszCompletePath, dwFFlags)
The combination of these two functions allows you to store file paths in a reasonably flexible way, since if a user or administrator moves something (such as a user profile or the system drive), these API routines make that change transparent to your application. |