Geoff Chappell - Software Analyst
This function loads the string resource that is indicated by a given indirect string.
HRESULT SHLoadIndirectString ( LPCWSTR pszSource, LPWSTR pszOutBuf, UINT cchOutBuf, PVOID *ppvReserved);
The pszSource argument provides the address of a null-terminated Unicode string to parse.
The pszOutBuf and cchOutBuf arguments describe a buffer, by address and capacity (in characters) respectively, which is to receive the resolved string. These arguments may be NULL and 0 if output is not required.
The ppvReserved argument is ignored.
The function returns zero for success, else an error code.
If pszSource is NULL, there is no input to parse. The function can do nothing, and fails (returning E_INVALIDARG).
If the input string does not begin with an @ sign, it is not an indirect string. If pszOutBuf is not NULL, the function produces as output a null-terminated copy of as much of the input string as will fit in the output buffer. Whether an output buffer is provided or not, the function succeeds. Though the copy is trivial if the input string and output buffer have the same address, the results of any other overlap are better treated as undefined.
If the input does begin with an @ sign, the function expects its output to differ from the input string. To ensure that the input string remains available even after output is produced, the function works with a copy of the input string. If the function cannot duplicate the input string, it fails (returning E_OUTOFMEMORY).
The indirect string is not necessarily the whole of the input string. It begins with the @ sign but extends only up to but not including the first question mark. If there is no question mark, the indirect string extends up to but not including the next @ sign. In its copy of the input string, the function ignores all characters that follow the indirect string.
If the indirect string does not contain “shell32.dll” (independent of case), then the indirect string may be cached in the registry, as a value in the key that has 0x5021 as its SHELLKEY. The function reads this value’s data, of whatever type, directly to the output buffer. Success at this is success for the function (including if pszOutBuf is NULL). As a side-effect of this caching at a well-known location, many well-known titles and messages that are loaded from resource strings in shell executables can be edited, in effect, without any change to those executables as files.
The indirect string may contain environment variables in the standard notation of enclosure by percent signs. The function expands environment variables in the indirect string, from immediately after the opening @ sign. A temporary buffer of 267 characters is allowed for the result. If the expansion does not fit, the function continues with as many characters of the expansion as do fit the temporary buffer. If the expansion fails, the function continues with as many characters of the indirect string as fit with no environment variables expanded.
With environment variables possibly expanded into this temporary buffer, the interpretation is then of a pathname to a resource library, a comma, a minus sign and a resource ID, in a standard notation defined by the documented function PathParseIconLocation. The size of temporary buffer suggests an allowance of MAX_PATH characters for a pathname and 5 decimal digits for a 16-bit resource ID. However, this is all copied into yet another buffer, just of MAX_PATH characters, before the parsing starts. If this is not large enough, the function fails (typically returning STRSAFE_E_INSUFFICIENT_BUFFER). If the parsing does not extract a resource ID, the function fails (returning E_FAIL). The pathname, stripped of enclosing double-quotes and then of leading and trailing spaces, is then examined to see if it is a path and filename or is just a filename.
If the pathname is just a filename and contains LC.DLL (independent of case), then the function loads the resource library using MLLoadLibrary (which may mean it actually loads from a language-specific subdirectory of wherever Internet Explorer is installed). Otherwise, the function loads the library just with LoadLibraryEx (with flags such that the executable is loaded only as a data file and image resource). Either way, once the library is loaded, the function loads the indicated string resource from that library directly to the output buffer. If the function fails to load the library or to load the string resource (including because pszOutBuf is NULL), it fails (returning E_FAIL).
If the indirect string did not contain “shell32.dll”, the function caches the loaded string resource into the registry key represented by SHELLKEY 0x5021. The indirect string is the value. The loaded string resource is its REG_SZ data. Success is merely desirable.
In all cases where the function fails except for lack of input, the function stores a null string in the output buffer unless cchOutBuf is zero.
The function allows explicitly in several places for pszOutBuf to be NULL, but not in enough places to support a clear interpretation of intention. When given no output buffer, the function can succeed if the input string does not begin with an @ sign or if the input string is cached, but not if the function gets as far as successfully loading the resource string. In this latter case, not only will the function try to return an error, it will fault unless cchOutBuf is zero. If the intention ever was to provide for a NULL pszOutBuf as a means to test whether the input string is a valid indirect string without caring to have the evaluation, then code for the case is not yet completed.
The preceding description is of the implementation in the SHLWAPI version 6.00 from Windows Vista.
Earlier builds do not defend against being given no input string. They also do not care if the indirect string contains “shell32.dll” for deciding whether an input string and its corresponding resource string may be cached. Be aware also that interpretation of SHELLKEY 0x5021 changes with Windows Vista:
Key: | HKEY_CURRENT_USER\Software\Microsoft\Windows\ShellNoRoam\MUICache | before Windows Vista |
HKEY_CURRENT_USER\Software\Classes\Local Settings\Software\Microsoft\Windows\Shell\MuiCache
HKEY_CURRENT_USER\Software\Microsoft\Windows\Shell\MuiCache |
Windows Vista and higher |
Builds for Internet Explorer 6.0 and Windows XP always consult the cache, but if they are running on Windows rather than NT, they do not necessarily save the resource string to the cache. The details depend on user-interface languages and are presently beyond the scope of this article.
The limit of MAX_PATH characters for the resource-location string (i.e., the indirect string, less its initial @ sign, and with environment variables possibly expanded) is new for Windows Vista, possibly as an oversight. Earlier builds also duplicate the resource-location string in order to parse it, but into new memory rather than into a fixed-size local buffer.
SHLWAPI supported indirect strings in yet earlier versions when something like this function was merely an internal procedure for the SHLoadRegUIString function. The present allowance for a second @ sign to terminate the indirect string is then seen as vestigial from an earlier provision for caching the string resource in the registry value from which the indirect string is loaded.
The SHLoadIndirectString function is exported by name from SHLWAPI.DLL version 6.00 and higher, and also as ordinal 487.
This function is documented, though the Minimum DLL Version is said to be “shlwapi.dll version 5.5 or later”.
Note that where Microsoft’s documentation would have you believe that “if the resource value is zero or greater, the number becomes the index of the string in the binary file”, the reality is that the function explicitly defends against this, and returns an error.