Nasty gotcha: SetThreadUILanguage cannot be used to restore the thread UI language

Suppose you want to change the thread UI language temporarily. You might be tempted to do this:

// Code in italics is wrong
void Something()
  // Save the original language.
  LANGID originalLanguage = GetThreadUILanguage();

  // Set a new language temporarily.

  // ... do stuff that uses the new language ...

  // Restore the original language.

This seems to work, but in fact it doesn't.

The Get­Thread­UI­Language function returns the first user interface language for the current thread. If a preferred language has been set for the thread, it will use that. Otherwise, it will follow a documented fallback algorithm.¹

On the other hand, Set­Thread­UI­Language sets the UI language for the current thread. It never sets the thread language back to "not set".

In the above code fragment, the result is that the thread UI language is locked to whatever the effective thread UI language was at the time the function was called, even if the fallback languages change.

For example, suppose the user's preferred language is English, the process's preferred language is German, and the thread has no preferred language. The call to Get­Thread­UI­Language will return German, and when the function tries to restore the original language, it sets the thread's preferred language to German. This is not the same as clearing the thread's preferred language, however. If the process changes its preferred language to Swedish, and the thread has no preferred language, then the effective language should change to Swedish. But the code fragment above explicitly sets the thread language to German, so the effective language will be German.

The way to restore the thread preferred UI language state is to capture the thread preferred UI languages with the Get­Thread­Preferred­UI­Languages function and restore them with the Set­Thread­Preferred­UI­Languages function. For more information, see my earlier discussion.

¹ The documentation for Get­Thread­UI­Language says

Calling this function is identical to calling Get­Thread­Preferred­UI­Languages with dwFlags set to MUI_MERGE_SYSTEM_FALLBACK | MUI_MERGE_USER_FALLBACK | MUI_LANGUAGE_ID and using the first language in the retrieved list.

You can then follow the bouncing ball to the documentation for Get­Thread­Preferred­UI­Languages and read the description of what happens when you ask for both system and user fallbacks.

Comments (6)

  1. David-T says:

    This all seems like a horribly complicated case of “using global state to solve a local problem”…

  2. Gee Law says:

    The comments are part of the code. The first comment is in italics and says “code in italics is wrong”. I love this example of Russell’s paradox :-)

    1. Are comments considered part of the code? They’re pretty much universally ignored by all code compilers, they don’t have any logic… they’re definitely part of the source, and could be considered “code” for documentation “compilers”.

      1. Gee Law says:

        To me, yes. I also did some random searching: Wikipedia’s definition of “comments” says they’re part of the source code that blahs. Also, a compiler toolchain will have to attend to comments to ignore them.

      2. smf says:

        The compiler doesn’t ignore them, they just produce no executable instructions. The compiler has to parse them to find where they end. A // (AKA single line comment) can have a line continuation character at the end and then it spills onto the next line.

        Of course you could argue that was part of the pre-processor and not the compiler, but that will just explode the argument even further.

        “code” is too vague a definition. Although in SLOC comments are specifically excluded.

      3. voo says:

        Considering that comments are part of the grammar of any programming language I know (and having incorrect comments in C/C++ is undefined behavior, so it can have effects on the generated assembly), I’d say it’s just fair to define them as part of the “code”.

Skip to main content