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
SetThreadPreferredUILanguages
!
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
SetThreadPreferredUILanguages
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 GetThreadPreferredUILanguages
,
that the list of languages I receive back represents the
clear state as opposed to indicating that some other
code called SetThreadPreferredUILanguages
before I did?”
The magic is the flag
MUI_THREAD_LANGUAGES
.
If you pass this flag when you call
GetThreadPreferredUILanguages
,
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
SetThreadPreferredUILanguages
function.
In fact, he has
several such thoughts.
0 comments