Thursday, January 18, 2007 12:01 AM
Michael S. Kaplan
What the %$#& is up with localized paths in Vista?
You know, South Park was just on, it was the one where they wind up taking a bus to China for the world dodgeball championship. I was trying to decide whether it was a good thing or not that I recognized 野種 on the banner in the back of the gym even though I know very little Chinese, and then I wondered whether KTSW really had cut the scene that had the banners in it or whether I had missed it.
I'm not sure why, but it got me thinking about a question from the Suggestion Box, where ph_arnaud asked:
Hi,
How are applications running on localized Vista supposed to show filepath names to the user with localized folder names?
For example:
At least in German Vista Ultimate and Home, a dir in command prompt shows:
C:\Program Files
C:\Users
C:\Windows
(not showing hidden or system items).
Windows Explorer shows:
C > Benutzer
C > Programme
C > Windows
If UI shows a confirmation message with a path name say to some user application data file using:
SHGetFolderPath ( CSIDL_MYPICTURES )
this function returns:
C:\Users\admin\Pictures
(I had hoped this would return the localized name, then all legacy code would show localized UI strings on Vista without more effort... and couldn't the hidden 'junctions' folders actually make the path a valid one? well a digression, it doesn't seem to work that way...)
Is this purpose of SHGetLocalizedName(), to convert a path such as returned by SHGetFolderPath to something the user would understand browsing with Windows Explorer?
C:\Betnutzer\admin\Bilder
I've tried using SHGetLocalizedName on the result of SHGetFolderPath but get an error (GetLastError returns 'Invalid window handle')
Is it worth trying to figure this out, because this is the solution to my question, or is another approach (different API function better)?
I would expect this would confuse average users, who don't use the command line, to see English folder names coming from their Vista applications, while Explorer shows localized names for directories.
However I noticed that German WordPad and Paint show the English names of directories in the MRU list in German Vista, which doesn't match my expectation for consistent folder names.
thanks for your insight on this.
Well, first let's put together a call to SHGetLocalizedName that works, just for grins so we know what it does (and that it can do it!):
using System;
using System.Text;
using System.Globalization;
using System.Runtime.InteropServices;
namespace Testing {
class TestGetLocalizedName {
[DllImport("shell32.dll", CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Unicode)]
internal static extern int SHGetLocalizedName(string pszPath, StringBuilder pszResModule, ref int cch, out int pidsRes);
[DllImport("user32.dll", EntryPoint="LoadStringW", CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Unicode)]
internal static extern int LoadString(IntPtr hModule, int resourceID, StringBuilder resourceValue, int len);
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, EntryPoint = "LoadLibraryExW")]
internal static extern IntPtr LoadLibraryEx(string lpFileName, IntPtr hFile, uint dwFlags);
internal const uint DONT_RESOLVE_DLL_REFERENCES = 0x00000001;
internal const uint LOAD_LIBRARY_AS_DATAFILE = 0x00000002;
[DllImport("kernel32.dll", ExactSpelling = true)]
internal static extern int FreeLibrary(IntPtr hModule);
[DllImport("kernel32.dll", EntryPoint="ExpandEnvironmentStringsW", CharSet=CharSet.Unicode, ExactSpelling=true)]
internal static extern uint ExpandEnvironmentStrings(string lpSrc, StringBuilder lpDst, int nSize);
[STAThread]
static void Main(string[] args) {
if(args.Length > 0) {
for(int i=0; i < args.Length; i++) {
StringBuilder sb = new StringBuilder(500);
int len, id;
len = sb.Capacity;
if(SHGetLocalizedName(args[i], sb, ref len, out id) == 0) {
Console.Write("Resource is in: \"");
Console.Write(sb.ToString());
Console.Write("\"; ID to load is: ");
Console.WriteLine(id);
ExpandEnvironmentStrings(sb.ToString(), sb, sb.Capacity);
IntPtr hMod = LoadLibraryEx(sb.ToString(), IntPtr.Zero, DONT_RESOLVE_DLL_REFERENCES | LOAD_LIBRARY_AS_DATAFILE);
if(hMod != IntPtr.Zero) {
if(LoadString(hMod, id, sb, sb.Capacity) != 0) {
Console.Write("which for ");
Console.Write(CultureInfo.CurrentUICulture.Name);
Console.Write(" is: ");
Console.WriteLine(sb.ToString());
}
FreeLibrary(hMod);
}
Console.WriteLine("\r\n");
} else {
Console.Write("The path ");
Console.Write(args[i]);
Console.Write(" is not localized.");
Console.WriteLine("\r\n");
}
}
}
}
}
}
Ok, just a silly little console app that you can pass a bunch of paths to, and it will return some stuff about them. Like here is what it returned for me when I switched my user interface language to French:
E:\>GetLocalizedNameTest.EXE "E:\Program Files" "E:\Users"
Resource is in: "%SystemRoot%\system32\shell32.dll"; ID to load is: 21781
which for fr-FR is: Programmes
Resource is in: "%SystemRoot%\system32\shell32.dll"; ID to load is: 21813
which for fr-FR is: Utilisateurs
Now anything that returns failure you can just use the path you had (though I noticed the function fails with paths that have trailing backslashes, which seems like a bug to me, and paths without a localized name, which does not, and mixed paths that have localized and nolocalized elements, which does since the function is documented as requiring full paths).
Ok, so it works. Kind of (note that the mixed path case is the one that ph_arnaud was running into).
Though it does leave a person wondering how they are supposed to get the full path name, doesn't it? It must work, since (after all) it works for the Shell. It is just not obvious how. Hmmm....
Never mind, let's move to the rest of the question, since I think otherwise the superficial functional difficulties here might distract us from the fundamental design problems. :-)
Once upon a time, when each language SKU had its own set of paths which might well be different from what they might be on an en-US copy, MUI simply left some of these "SKU-specific items" whose underlying resources did not change when the UI language did, people complained (with good reason, in my opinion) that localized systems were not the same as an MUI system set to that user interface language.
Functions like SHGetFolderPath had the job of giving you a functioning folder you could use in your code (because if you hard coded the folder names they might fail completely!). Their central intent was not so much for nice display (though that worked too) as it was for core functionality of paths resolving at all.
This is an important point so please read the previous paragraph again.
But is this always true? Weren't there some folders which did vary with UI language that this function did show those changes for? Perhaps I am misremembering. I don't have an XP MUI machine right in front of ne to test this on. Hmmm....
Now of course, when every SKU is the same, one has "lost" the "feature" of a command prompt that gave localized names. And one has to decide whether it is most evil to break the ability of SHGetFolderPath to return properly localized paths or whether it is insdtead most evil for it to return paths that will function.
They decided to go for the functionality, it would seem.
Though at the same time some functionality has been lost here in each individual SKUs for there to be a better overall architecture.
For what it is worth, I agree with ph_arnaud that there could have been some better design choices here. And I had trouble getting nested paths that involved both localized and non-localized elements to work properly, which I assume was simply due to the fact that I threw the code together at 11:30pm for a blog post that I was trying to get up around midnight.
(Before I forget, I need to remember to send email to the WMI folks to let them know a bunch of their pinvoke declarations are incorrect!)
I also agree that the inconsistencies are weird, just like they are in this other post.
I'll be talking more about some of the issues here (both the ones problems that were addressed and the problems that have been introduced) in upcoming posts....
This post brought to you by ፨ (U+1368, a.k.a. ETHIOPIC PARAGRAPH SEPARATOR)