Geoff Chappell - Software Analyst
This function loads an MUI satellite library for a given executable (or library).
HINSTANCE MLLoadLibrary ( LPCTSTR lpszLibFileName, HMODULE hModule, DWORD dwCrossCodePage);
The function exists in ANSI and Unicode forms.
The lpszLibFileName argument names the satellite, either as a filename or relative pathname. If the latter, the path component may be ignored.
The hModule argument is a handle to a loaded executable (or library), or may be NULL as if to represent IEXPLORE.EXE in the Internet Explorer directory.
The low 3 bits (as masked by ML_CROSSCODEPAGE_MASK) of the dwCrossCodePage argument direct the function in its choice of language. Meaningful values are:
0 | ML_NO_CROSSCODEPAGE | use the MUI language only if safe |
1 | ML_CROSSCODEPAGE_NT | use the MUI language if running on NT; else use the MLUI language only if safe |
2 | ML_CROSSCODEPAGE | use the MUI language |
4 | ML_SHELL_LANGUAGE | use the shell language |
The function returns a handle to the loaded library, else NULL for failure.
The MLLoadLibrary function supports an early scheme of Multilingual User Interface. An executable has its localised resources separated into satellite libraries, one for each supported language, so that the executable itself is language-neutral. All satellites have the same filename. A default satellite may be in the same directory as the executable. Others are in language-specific subdirectories of the MUI subdirectory of the Internet Explorer installation. This function loads what it considers to be the appropriate satellite.
The function first queries the registry for a parameter that it may use:
Key: | HKEY_LOCAL_MACHINE\Software\Microsoft\Internet Explorer\International |
Value: | CheckVersion |
Type: | REG_DWORD or four bytes of REG_BINARY |
Default: | 1 |
Any non-zero data of either expected type is interpreted as TRUE. If the data is not successfully queried, the function uses the data from the last successful query, or defaults to TRUE.
If pszLibFileName is NULL, then the function has no library to look for, and fails.
Crucial to the choice of satellite is a determination of the appropriate language. The function decides on what Microsoft’s symbol files show to be called (by Microsoft’s programmers) the normalised language. In any of the following cases, the normalised language is simply the user default UI language:
Otherwise, or if the user default UI language is not defined, the function falls back to the system default UI language as the normalised language.
For the function even to consider locating the library relative to the given module, it is necessary that hModule be not NULL and that a pathname of no more than MAX_PATH characters can be determined for it. The module-based pathname for the library is then obtained just by substituting whatever is at pszLibFileName for the module’s filename, provided that the result keeps to MAX_PATH characters. (The function does not require that the string at pszLibFileName be just a filename, in this case.)
The Internet Explorer directory is knowable from the registry:
Key: | HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\App Paths\iexplore.exe |
Value: | default |
The MUI pathname for the library is iepath\mui\langid\libfilename, in which iepath is the data for the default value of that key, less its filename component, langid is a 4-digit hexadecimal code for the normalised language, and libfilename is the filename component from the pszLibFileName argument. (The function ignores a path in the string at pszLibFileName, in this case.)
If the MUI pathname does not fit MAX_PATH characters, the function uses as much as does fit. If the location of IEXPLORE.EXE cannot be read from the registry, the function attempts to deal with it by taking the single character 0 as the iepath, but it does not actually terminate this default iepath as a string and this article prefers to regard this case as having undefined consequences.
The function has the following algorithm for choosing between the module-based and MUI locations:
Separation of localised resources into satellite libraries to let the executable be language-neutral requires that all the satellites and the executable agree on all the resource IDs. The function does not accept the MUI pathname unless it satisfies itself about this by checking the version resources in the two possible libraries, i.e., at the module-based and MUI pathnames. This cannot even be attempted unless both pathnames have been computed, and it is disabled if CheckVersion evaluated as false.
If version-checking is performed, then either of the following is required of the library at the MUI path:
The version comparison applies to all eight bytes of version number as learnt from the dwFileVersionMS and dwFileVersionLS members of the VS_FIXEDFILEINFO structure in the root block of the version-information resource. The range of satisfactory versions is read from the registry value
Key: | HKEY_LOCAL_MACHINE\Software\Microsoft\Internet Explorer\International |
Value: | exefilename |
where exefilename is the filename of the current process’s executable. The function accepts up to MAX_PATH characters of string data or MAX_PATH bytes of any data type provided it has a null byte. The interpretation is of a minimum version number, a hyphen and a maximum version number. Characters between the minimum version number and the hyphen are ignored, as are characters after the maximum version number. Each version number is up to four numerical fields separated by periods. Each field consists of as many decimal digits as follow without interruption. Characters between the decimal digits and any period that follows are ignored. Evaluation is modulo 64K. Missing fields evaluate as zero. Fields are ordered from most significant to least. The four fields together make an eight-byte version number. If the corresponding eight-byte version number from the version-information resource of the library at the MUI path is not between the minimum and maximum, inclusively, the version checking fails and the MUI path is not accepted.
For an example of the registry range in Microsoft’s real-world practice, see the Knowledge Base article MUI functionality in Outlook Express 6.0 SP1 is affected by the MS03-014 cumulative update for Outlook Express.
The function then tries to load the satellite library at the selected pathname. If this fails and the selected pathname is the MUI pathname, the function tries to load the library using the module-based pathname. (If none is known, the function uses an empty string at the pathname.) If even this fails, the function tries one last time to load the library, with the pathname this time being whatever was given as pszLibFileName.
Except if the function fails due to pszLibFileName being NULL, it records its returned HINSTANCE (from the last call to LoadLibrary) and the chosen language ID. This is ordinarily the normalised language, but is the system default UI language if the function has tried to load the library from the module-based pathname. The recording is done through the MLSetMLHInstance function and must be undone by calling MLClearMLHInstance, as when calling MLFreeLibrary. The record can be consulted by MLIsMLHInstance, though only to test if the given library is loaded as a multilingual library, not to find the language that was chosen.
The preceding description is for the SHLWAPI version 6.00 from Windows Vista. Many variations are known for earlier builds and versions.
In all earlier builds and versions, the CheckVersion value is a boolean for which the usual string data is also accepted. Moreover, the function looks for the CheckVersion value only on the first execution. That the build for Windows Vista reads this value on every execution is a coding oversight introduced by some programmer’s change to using the (relatively) new function SHRegGetValue where the old code uses SHRegGetBoolUSValue. The latter returns the boolean data and the code stores it to a static variable as that variable’s initialisation, such that the compiler generates a so-called static guard against multiple initialisation. The new function returns an error code. On a successful return, the code stores the dword of data to the same variable as before, but not in a way that counts as initialising the variable. The compiler correctly does not generate a guard and the value is therefore read on every execution.
Builds of version 6.00 from Windows XP and Internet Explorer 6.0, and all earlier versions, have a more elaborate determination of the normalised language (which makes greater use of the dwCrossCodePage argument).
If the meaningful bits of dwCrossCodePage are ML_SHELL_LANGUAGE, then the normalised language is the shell language, if a non-zero language ID can be determined for it. Otherwise, the function falls back to the install language.
Most other values for the meaningful bits of dwCrossCodePage select the MUI language as the normalised language. Again, if no non-zero language ID is found this way, the function falls back to the install language. This fallback to the install language is forced if any of the following is true:
unless at least one of several other conditions is met:
Builds of version 6.00 for Windows XP and Internet Explorer 6.0, and all earlier versions, do not check that the module-based pathname fits the allowance of MAX_PATH characters without truncation. The builds of version 6.00 for Windows Server 2003 check, but a coding oversight means that a truncated pathname does not get completely rejected. The function will still try to use the truncated pathname for version-checking and as the fallback if the library cannot be loaded from the MUI pathname. This is fixed in the version 6.00 from Windows Vista.
Support for a registry value to provide a range of MUI-compatible versions begins with the version 6.00 from Windows XP SP1 (and the roughly contemporaneous version 6.00 from Internet Explorer 6.0 SP1).
The MLLoadLibrary function is exported from SHLWAPI.DLL as ordinals 377 and 378 (for ANSI and Unicode forms, respectively) in version 5.00 and higher. It is also exported by name in the version 6.00 from Windows Vista, and higher.
Documentation of this function by Microsoft has a curious history. Editions of the MSDN Library on CD from both January 2001 and January 2004 give the function two more arguments than it is known ever to have as an exported function. Until such errors were corrected, the function may just as well not have been documented at all. The obvious companion function MLFreeLibrary didn’t get documented until some time duing 2004. Perhaps Microsoft regarded both functions as internal to Internet Explorer.