December 23rd, 2010

What is the correct way of temporarily changing a thread's preferred UI language?

A customer ran into a crashing bug in their shell extension. The shell extension wants to change the thread’s preferred UI language temporarily, so that it can load its resources from a specific language. You’d think this would be easy:

// error checking elided for simplicity
// There is a bug in this code - read on
// Get the current thread preferred UI languages
ULONG cLanguages;
PZZWSTR pszzPrevLanguages;
ULONG cchPrevLanguages = 0;
GetThreadPreferredUILanguages(MUI_LANGUAGE_NAME,
                              &cLanguages, NULL,
                              &cchPrevLanguages);
pszzPrevLanguages = new WCHAR[cchPrevLanguages];
GetThreadPreferredUILanguages(MUI_LANGUAGE_NAME,
                              &cLanguages,
                              pszzPrevLanguages,
                              &cchPrevLanguages);
... change the thread preferred UI languages ...
... load resources ...
// Restore the original thread preferred UI languages
SetThreadPreferredUILanguages(MUI_LANGUAGE_NAME,
                              pszzPrevLanguages,
                              &cLanguages);
delete[] pszzPrevLanguages;

Approximately ten seconds after this code runs, Explorer crashes with the exception STATUS_CALLBACK_RETURNED_LANG whose description is “A threadpool worker thread enter a callback, which left with preferred languages set. This is unexpected, indicating that the callback missed clearing them.” (Just before Explorer crashes, the message “ThreadPool: callback 77180274(05B67430) returned with preferred languages set” appears on the debugger, which says basically the same thing as the status code.)

Exercise: Why does it take ten seconds before the crash occurs?

This crash is puzzling, because it’s claiming that the callback didn’t reset the thread preferred languages, but you can see us doing it right there in the code when we call Set­Thread­Preferred­UI­Languages! Somebody’s on crack, but who?

A closer reading of the error message indicates that the callback needs to “clear” the thread preferred languages, not merely reset them to their original values, and the documentation for Set­Thread­Preferred­UI­Languages says, “To clear the thread preferred UI languages list, the application can set this parameter to a null string or an empty double null-terminated string.” Okay, so now the question is, “How can I tell, when I call Get­Thread­Preferred­UI­Languages, that the list of languages I receive back represents the clear state as opposed to indicating that some other code called Set­Thread­Preferred­UI­Languages before I did?”

The magic is the flag MUI_THREAD_LANGUAGES. If you pass this flag when you call Get­Thread­Preferred­UI­Languages, it will return a null string if the thread has not customized its preferred UI languages, indicating that the way to restore the thread’s preferred UI language state is to clear it rather than setting it. Fortunately, this lines up nicely with the way you’re supposed to clear the state, so at the end of the day there is no special case.

The fix to the above code, then, is to make the following simple change:

// error checking elided for simplicity
// Get the current thread preferred UI languages
ULONG cLanguages;
PZZWSTR pszzPrevLanguages;
ULONG cchPrevLanguages = 0;
GetThreadPreferredUILanguages(MUI_LANGUAGE_NAME |
                              MUI_THREAD_LANGUAGES,
                              &cLanguages, NULL,
                              &cchPrevLanguages);
pszzPrevLanguages = new WCHAR[cchPrevLanguages];
GetThreadPreferredUILanguages(MUI_LANGUAGE_NAME |
                              MUI_THREAD_LANGUAGES,
                              &cLanguages,
                              pszzPrevLanguages,
                              &cchPrevLanguages);
... change the thread preferred UI languages ...
... load resources ...
// Restore the original thread preferred UI languages
SetThreadPreferredUILanguages(MUI_LANGUAGE_NAME,
                              pszzPrevLanguages,
                              &cLanguages);
delete[] pszzPrevLanguages;

As you might expect, Michael Kaplan has his own thoughts on the Set­Thread­Preferred­UI­Languages function. In fact, he has several such thoughts.

Topics
Code

Author

Raymond has been involved in the evolution of Windows for more than 30 years. In 2003, he began a Web site known as The Old New Thing which has grown in popularity far beyond his wildest imagination, a development which still gives him the heebie-jeebies. The Web site spawned a book, coincidentally also titled The Old New Thing (Addison Wesley 2007). He occasionally appears on the Windows Dev Docs Twitter account to tell stories which convey no useful information.

0 comments

Discussion are closed.