向应用程序添加多语言用户界面支持

本教程演示了如何利用单一语种应用程序并将其改造成全球适用的应用程序。 此应用程序采用在 Microsoft Visual Studio 中构建的完整解决方案的形式。

概述

从 Windows Vista 开始,Windows 操作系统已从头开始构建为多语言平台,新增的支持功能允许用户创建使用 Windows MUI 资源模型的多语言应用程序。

本教程从以下几个方面介绍了对多语言应用程序的新支持:

  • 使用改进的 MUI 平台支持轻松启用多语言应用程序。
  • 扩展多语言应用程序,支持它们在 Windows Vista 之前的 Windows 版本上运行。
  • 说明开发专用多语言应用程序(如控制台应用程序)的其他注意事项。

这些链接有助于快速了解国际化和 MUI 的概念:

Hello MUI 背后的理念

你可能熟悉经典的 Hello World 应用程序,它演示了基本的编程概念。 本教程采用类似的方法来说明如何使用 MUI 资源模型更新单语言应用程序以创建名为 Hello MUI 的多语言版本。

注意

本教程中的任务用详细步骤进行了介绍,因为这些活动必须执行得很精确,并且有必要向对这些任务缺乏经验的开发人员说明详细信息。

 

设置 Hello MUI 解决方案

这些步骤概述了创建 Hello MUI 解决方案的准备工作。

平台要求

必须使用适用于 Windows 7 和 Visual Studio 2008 的 Windows 软件开发工具包 (SDK) 编译本教程中的代码示例。 Windows 7 SDK 将安装在 Windows XP、Windows Vista 和 Windows 7 上,示例解决方案可以基于上述任何操作系统版本构建。

本教程中的所有代码示例都可在 x86 和 x64 版本的 Windows XP、Windows Vista 和 Windows 7 上执行。 在适用位置,标注了无法在 Windows XP 上运行的特定部分。

先决条件

  1. 安装 Visual Studio 2008。

    有关详细信息,请参阅 Visual Studio 开发人员中心

  2. 安装适用于 Windows 7 的 Windows SDK。

    可以从 Windows 开发人员中心的 Windows SDK 页面安装它。 SDK 为 Windows XP 及更新的操作系统提供开发应用程序的实用程序。

    注意

    如果未将包安装到默认位置,或者未在系统驱动器(通常是 C 驱动器)上安装,请记下安装路径。

     

  3. 配置 Visual Studio 命令行参数。

    1. 打开 Visual Studio 命令窗口。
    2. 键入 set path。
    3. 确认路径变量包含 Windows 7 SDK 的 bin 文件夹的路径:...Microsoft SDKs\Windows\v7.0\bin
  4. 安装 Microsoft NLS 下层 API 加载项包。

    注意

    在本教程的上下文中,只有在要将应用程序自定义为在 Windows Vista 之前的 Windows 版本上运行时,才需要此包。 请参阅步骤 5:自定义 Hello MUI

    1. 下载并安装包,Microsoft 下载中心不再提供该包。 在 Windows 10 2019 年 5 月更新及更高版本上使用 ICU 全球化 API

    2. 与 Windows SDK 一样,如果未将包安装到默认位置,或者未在系统驱动器(通常是 C 驱动器)上安装,请记下安装路径。

    3. 如果开发平台是 Windows XP 或 Windows Server 2003,请确认是否已正确安装并注册 Nlsdl.dll。

      1. 浏览到安装路径位置下的“redist”文件夹。
      2. 运行相应的可再发行程序包 Nlsdl.*.exe,例如 nlsdl.x86.exe。 此步骤将安装并注册 Nlsdl.dll。

    注意

    如果你开发使用 MUI 且必须在 Windows Vista 之前的 Windows 版本上运行的应用程序,则目标 Windows 平台上必须已安装 Nlsdl.dll。 在大多数情况下,这意味着应用程序需要包含并安装可再发行的 Nlsdl 安装程序(而不仅仅是复制 Nlsdl.dll 本身)。

     

步骤 0:创建硬编码的 Hello MUI

本教程从 Hello MUI 应用程序的单语版本开始。 应用程序假定使用 C++ 编程语言、宽字符字符串和 MessageBoxW 函数进行输出。

让我们首先创建初始 GuiStep_0 应用程序,以及包含本教程中所有应用程序的 HelloMUI 解决方案。

  1. 在 Visual Studio 2008 中创建一个新项目。 使用以下设置和值:

    1. 项目类型:在 Visual C++ 下选择 Win32,然后在 Visual Studio 安装的模板下选择 Win32 项目。
    2. 名称:GuiStep_0。
    3. 位置:ProjectRootDirectory(后续步骤会引用此目录)。
    4. 解决方案名称:HelloMUI。
    5. 选择“创建解决方案的目录”。
    6. 在 Win32 应用程序向导中,选择默认应用程序类型:Windows 应用程序。
  2. 配置项目线程模型:

    1. 在“解决方案资源管理器”中右键单击 GuiStep_0 项目,并选择“属性”。

    2. 在项目的“属性页”对话框中:

      1. 在左上角的下拉列表中,将“配置”设置为“所有配置”。
      2. 在“配置属性”下,展开 C/C++,选择“代码生成”,并设置“运行时库”:多线程调试 (/MTd)。
  3. 将 GuiStep_0.cpp 的内容替换为以下代码:

    // GuiStep_0.cpp : Defines the entry point for the application.
    //
    
    #include "stdafx.h"
    #include "GuiStep_0.h"
    
    int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
    {
        UNREFERENCED_PARAMETER(hInstance);
        UNREFERENCED_PARAMETER(hPrevInstance);
        UNREFERENCED_PARAMETER(lpCmdLine);
        UNREFERENCED_PARAMETER(nCmdShow);
    
        MessageBoxW(NULL,L"Hello MUI",L"HelloMUI!",MB_OK | MB_ICONINFORMATION);
        return 0;
    }
    
  4. 生成并运行应用程序。

上面的简单源代码对用户将看到的所有输出(在本例中为文本“Hello MUI”)进行了硬编码或嵌入的简单设计选择。 对于不认识英语单词“Hello”的用户来说,此选择限制了应用程序的实用性。由于 MUI 是一种基于技术的英语首字母缩略词,因此本教程假定该字符串对所有语言都显示为“MUI”。

步骤 1:实现基本资源模块

Microsoft Win32 长期以来一直为应用程序开发人员提供将其 UI 资源数据与应用程序源代码分离的功能。 这种分离采用 Win32 资源模型的形式,其中通常显示给用户的符串、位图、图标、消息和其他项目会打包到可执行文件的不同部分,与可执行代码分离。

为了说明可执行代码和资源数据打包之间的这种分离,在此步骤中,本教程会将以前硬编码的“Hello”字符串(“en-US”资源)放入 HelloModule_en_us 项目中 DLL 模块的资源部分。

此 Win32 DLL 还可以包含库类型可执行功能(与任何其他 DLL 一样)。 但是,为了让用户专注于与 Win32 资源相关的方面,我们在 dllmain.cpp 中剔除了运行时 DLL 代码。 本教程的后续部分会使用此处生成的 HelloModule 资源数据,而且还会提供适当的运行时代码。

若要构造 Win32 资源模块,请首先使用已剔除运行时 DLL 代码的 dllmain 创建 DLL:

  1. 将新项目添加到 HelloMUI 解决方案:

    1. 在“文件”菜单上,依次选择“添加”和“新建项目”。
    2. 项目类型:Win32 Project。
    3. 名称:HelloModule_en_us。
    4. 位置:ProjectRootDirectory\HelloMUI。
    5. 在 Win32 应用程序向导中,选择应用程序类型:DLL。
  2. 配置此项目的线程模型:

    1. 在解决方案资源管理器中,右键单击项目 HelloModule_en_us 并选择“属性”。

    2. 在项目的“属性页”对话框中:

      1. 在左上角的下拉列表中,将“配置”设置为“所有配置”。
      2. 在“配置属性”下,展开 C/C++,选择“代码生成”,并设置“运行时库”:多线程调试 (/MTd)。
  3. 检查 dllmain.cpp。 (最好将 UNREFERENCED_PARAMETER 宏添加到生成的代码中,如此处所示,以便能够在警告级别 4 进行编译。)

    // dllmain.cpp : Defines the entry point for the DLL application.
    #include "stdafx.h"
    
    BOOL APIENTRY DllMain( HMODULE hModule,
                           DWORD  ul_reason_for_call,
                           LPVOID lpReserved
                         )
    {
        UNREFERENCED_PARAMETER(hModule);
        UNREFERENCED_PARAMETER(lpReserved);
    
        switch (ul_reason_for_call)
        {
        case DLL_PROCESS_ATTACH:
        case DLL_THREAD_ATTACH:
        case DLL_THREAD_DETACH:
        case DLL_PROCESS_DETACH:
            break;
        }
        return TRUE;
    }
    
  4. 将资源定义文件 HelloModule.rc 添加到项目:

    1. 在项目 HelloModule_en_us 下,右键单击“资源文件”文件夹,然后依次选择“添加”和“新建项”。

    2. 在“添加新项”对话框中,选择以下项:

      1. 类别:在 Visual C++ 下选择“资源”,然后在“Visual Studio 已安装的模板”下选择“资源文件” (.rc)。
      2. 名称:HelloModule。
      3. 位置:接受默认位置。
      4. 单击“添加”。
    3. 指定将新的 HelloModule.rc 文件保存为 Unicode 格式:

      1. 在解决方案资源管理器中,右键单击 HelloModule.rc,然后选择“查看代码”。
      2. 如果看到一条消息告知文件已打开,并询问是否要关闭该文件,请单击“是”。
      3. 将文件显示为文本后,在菜单中选择“文件”和“高级保存选项”。
      4. 在“编码”下,指定“Unicode - 代码页 1200”。
      5. 单击“确定”。
      6. 保存并关闭 HelloModule.rc。
    4. 添加包含“Hello”字符串的字符串表:

      1. 在资源视图中,右键单击 HelloModule.rc,然后选择“添加资源”。

      2. 选择“字符串表”。

      3. 单击 “新建” 。

      4. 将字符串添加到字符串表:

        1. ID:HELLO_MUI_STR_0。
        2. 值:0.
        3. 描述文字:你好。

      如果现在以文本形式查看 HelloModule.rc,你将看到各种特定于资源的源代码段。 最有趣的是描述“Hello”字符串的部分:

      /////////////////////////////////////////////////////////////////////////////
      //
      // String Table
      //
      
      STRINGTABLE 
      BEGIN
          HELLO_MUI_STR_0         "Hello"
      END
      

      此“Hello”字符串是需要本地化(即翻译)到应用程序希望支持的每种语言的资源。 例如,HelloModule_ta_in 项目(将在下一步中创建)将包含自己的针对“ta-IN”的 HelloModule.rc 本地化版本:

      /////////////////////////////////////////////////////////////////////////////
      //
      // String Table
      //
      
      STRINGTABLE 
      BEGIN
          HELLO_MUI_STR_0         "வணக்கம்"
      END
      
    5. 生成 HelloModule_en_us 项目并确保没有错误。

  5. 在 HelloMUI 解决方案中再创建六个项目(或任意数量的项目),以便为其他语言创建另外六个资源 DLL。 使用此表中的值:

    项目名称 .rc 文件的名称 字符串 ID 字符串值 字符串描述文字
    HelloModule_de_de HelloModule HELLO_MUI_STR_0 0 Hallo
    HelloModule_es_es HelloModule HELLO_MUI_STR_0 0 Hola
    HelloModule_fr_fr HelloModule HELLO_MUI_STR_0 0 Bonjour
    HelloModule_hi_in HelloModule HELLO_MUI_STR_0 0 नमस्
    HelloModule_ru_ru HelloModule HELLO_MUI_STR_0 0 Здравствуйте
    HelloModule_ta_in HelloModule HELLO_MUI_STR_0 0 வணக்கம்

     

有关 .rc 文件结构和语法的详细信息,请参阅关于资源文件

步骤 2:构建基本资源模块

使用以前的资源模型,构建七个 HelloModule 项目中的任何一个都会生成七个单独的 DLL。 每个 DLL 将包含一个资源节,其中包含本地化为适当语言的单个字符串。 虽然适用于从前的 Win32 资源模型,但此设计并未对 MUI 加以利用。

在 Windows Vista SDK 及更高版本中,MUI 提供了将可执行文件拆分为源代码和可本地化内容模块的功能。 通过其他自定义(稍后将在步骤 5 中介绍),可让应用程序支持多语言,以便在 Windows Vista 之前的版本上运行。

从 Windows Vista 开始,可用于从可执行代码中分离资源的主要机制包括:

  • 使用带有特定开关的 rc.exe(RC 编译器),或
  • 使用名为 muirct.exe 的生成后样式拆分工具。

有关详细信息,请参阅资源实用工具

为简单起见,本教程使用 muirct.exe 来拆分“Hello MUI”可执行文件。

拆分各种语言资源模块:概述

对于本教程中支持的七种语言中的每一种,将 DLL 拆分为一个可执行文件 HelloModule.dll 和一个 HelloModule.dll.mui 涉及一个多部分过程。 此概述描述了所需的步骤;下一部分将介绍执行这些步骤的命令文件。

首先,使用以下命令拆分只有英语语言的 HelloModule.dll 模块:

mkdir .\en-US
muirct.exe -q DoReverseMuiLoc.rcconfig -v 2 -x 0x0409 -g 0x0407 .\HelloModule_en_us.dll .\HelloModule.dll .\en-US\HelloModule.dll.mui

上述命令行使用配置文件 DoReverseMuiLoc.rcconfig。 muirct.exe 通常使用此类型的配置文件在中性语言 (LN) DLL 和从属语言 .mui 文件之间拆分资源。 在此情况下,DoReverseMuiLoc.rcconfig xml 文件(将在下一部分中介绍)表示许多资源类型,但它们都属于“localizedResources”或 .mui 文件类别,并且没有中性语言类别中的资源。 有关如何准备资源配置文件的详细信息,请参阅准备资源配置文件

除了创建包含英语字符串“Hello”的 HelloModule.dll.mui 文件外,muirct.exe 还会在拆分期间将 MUI 资源嵌入 HelloModule.dll 模块。 为了在运行时正确加载特定语言 HelloModule.dll.mui 模块中的相应资源,每个 .mui 文件必须修复其校验和,以匹配基线中性语言 LN 模块中的校验和。 此操作由如下命令完成:

muirct.exe -c HelloModule.dll -e en-US\HelloModule.dll.mui

同样,将调用 muirct.exe 为每种其他语言创建 HelloModule.dll.mui 文件。 但是,在这些情况下,中性语言 DLL 将被丢弃,因为只需要创建的第一个 DLL。 处理西班牙语和法语资源的命令如下所示:

mkdir .\es-ES
muirct.exe -q DoReverseMuiLoc.rcconfig -v 2 -x 0x0C0A -g 0x0407 .\HelloModule_es_es.dll .\HelloModule_discard.dll .\es-ES\HelloModule.dll.mui
muirct.exe -c HelloModule.dll -e es-ES\HelloModule.dll.mui

mkdir .\fr-FR
muirct.exe -q DoReverseMuiLoc.rcconfig -v 2 -x 0x040C -g 0x0407 .\HelloModule_fr_fr.dll .\HelloModule_discard.dll .\fr-FR\HelloModule.dll.mui
muirct.exe -c HelloModule.dll -e fr-FR\HelloModule.dll.mui

在上面的 muirct.exe 命令行中要注意的最重要事项之一是使用了 -x 标志来指定目标语言 ID。 提供给 muirct.exe 的值对于每个特定语言 HelloModule.dll 模块都是不同的。 此语言值非常重要,muirct.exe 在拆分期间使用该值对 .mui 文件做适当标记。 不正确的值会造成该特定 .mui 文件在运行时出现资源加载失败。 有关将语言名称映射到 LCID 的更多详细信息,请参阅语言标识符常量和字符串

每个拆分的 .mui 文件最终都会放置在与其语言名称对应的目录中,紧接在中性语言 HelloModule.dll 所在的目录下。 有关 .mui 文件放置的详细信息,请参阅应用程序部署

拆分各种语言资源模块:创建文件

在本教程中,你将创建一个包含拆分各种 DLL 的命令的命令文件,并手动调用它。 请注意,在实际开发工作中,可以通过将这些命令作为预生成或生成后事件包含在 HelloMUI 解决方案中来减少出现生成错误的可能性,但这超出了本教程的范围。

为调试版本创建文件:

  1. 创建包含以下命令的命令文件 DoReverseMuiLoc.cmd:

    mkdir .\en-US
    muirct.exe -q DoReverseMuiLoc.rcconfig -v 2 -x 0x0409 -g 0x0407 .\HelloModule_en_us.dll .\HelloModule.dll .\en-US\HelloModule.dll.mui
    muirct.exe -c HelloModule.dll -e en-US\HelloModule.dll.mui
    
    mkdir .\de-DE
    muirct.exe -q DoReverseMuiLoc.rcconfig -v 2 -x 0x0407 -g 0x0407 .\HelloModule_de_de.dll .\HelloModule_discard.dll .\de-DE\HelloModule.dll.mui
    muirct.exe -c HelloModule.dll -e de-DE\HelloModule.dll.mui
    
    mkdir .\es-ES
    muirct.exe -q DoReverseMuiLoc.rcconfig -v 2 -x 0x0C0A -g 0x0407 .\HelloModule_es_es.dll .\HelloModule_discard.dll .\es-ES\HelloModule.dll.mui
    muirct.exe -c HelloModule.dll -e es-ES\HelloModule.dll.mui
    
    mkdir .\fr-FR
    muirct.exe -q DoReverseMuiLoc.rcconfig -v 2 -x 0x040C -g 0x0407 .\HelloModule_fr_fr.dll .\HelloModule_discard.dll .\fr-FR\HelloModule.dll.mui
    muirct.exe -c HelloModule.dll -e fr-FR\HelloModule.dll.mui
    
    mkdir .\hi-IN
    muirct.exe -q DoReverseMuiLoc.rcconfig -v 2 -x 0x0439 -g 0x0407 .\HelloModule_hi_in.dll .\HelloModule_discard.dll .\hi-IN\HelloModule.dll.mui
    muirct.exe -c HelloModule.dll -e hi-IN\HelloModule.dll.mui
    
    mkdir .\ru-RU
    muirct.exe -q DoReverseMuiLoc.rcconfig -v 2 -x 0x0419 -g 0x0407 .\HelloModule_ru_ru.dll .\HelloModule_discard.dll .\ru-RU\HelloModule.dll.mui
    muirct.exe -c HelloModule.dll -e ru-RU\HelloModule.dll.mui
    
    mkdir .\ta-IN
    muirct.exe -q DoReverseMuiLoc.rcconfig -v 2 -x 0x0449 -g 0x0407 .\HelloModule_ta_in.dll .\HelloModule_discard.dll .\ta-IN\HelloModule.dll.mui
    muirct.exe -c HelloModule.dll -e ta-IN\HelloModule.dll.mui
    pause
    
  2. 创建包含以下行的 xml 文件 DoReverseMuiLoc.rcconfig:

    <?xml version="1.0" encoding="utf-8"?>
        <localization>
            <resources>
                <win32Resources fileType="Application">
                    <neutralResources>
                    </neutralResources>
                    <localizedResources>
                        <resourceType typeNameId="#1"/>
                        <resourceType typeNameId="#10"/>
                        <resourceType typeNameId="#1024"/>
                        <resourceType typeNameId="#11"/>
                        <resourceType typeNameId="#12"/>
                        <resourceType typeNameId="#13"/>
                        <resourceType typeNameId="#14"/>
                        <resourceType typeNameId="#15"/>
                        <resourceType typeNameId="#16"/>
                        <resourceType typeNameId="#17"/>
                        <resourceType typeNameId="#18"/>
                        <resourceType typeNameId="#19"/>
                        <resourceType typeNameId="#2"/>
                        <resourceType typeNameId="#20"/>
                        <resourceType typeNameId="#2110"/>
                        <resourceType typeNameId="#23"/>
                        <resourceType typeNameId="#240"/>
                        <resourceType typeNameId="#3"/>
                        <resourceType typeNameId="#4"/>
                        <resourceType typeNameId="#5"/>
                        <resourceType typeNameId="#6"/>
                        <resourceType typeNameId="#7"/>
                        <resourceType typeNameId="#8"/>
                        <resourceType typeNameId="#9"/>
                        <resourceType typeNameId="HTML"/>
                        <resourceType typeNameId="MOFDATA"/>
                    </localizedResources>
                </win32Resources>
            </resources>
        </localization>
    
  3. 将 DoReverseMuiLoc.cmd 和 DoReverseMuiLoc.rcconfig 复制到 ProjectRootDirectory\HelloMUI\Debug。

  4. 打开 Visual Studio 2008 命令提示符并导航到调试目录。

  5. 运行 DoReverseMuiLoc.cmd。

创建发布版本时,将相同的 DoReverseMuiLoc.cmd 和 DoReverseMuiLoc.rcconfig 文件复制到 Release 目录,并在那里运行命令文件。

步骤 3:创建具有资源洞察力的“Hello MUI”

基于上述初始硬编码的 GuiStep_0.exe 示例进行构建,你可以选择合并 Win32 资源模型,将应用程序的覆盖范围扩展到多语言用户。 此步骤中显示的新运行时代码包括模块加载 (LoadLibraryEx) 和字符串检索 (LoadString) 逻辑。

  1. 使用以下设置和值将新项目添加到 HelloMUI 解决方案(使用菜单选择“文件”、“添加”和“新建项目”):

    1. 项目类型:Win32 Project。
    2. 名称:GuiStep_1。
    3. 位置:接受默认位置。
    4. 在 Win32 应用程序向导中,选择默认应用程序类型:Windows 应用程序。
  2. 将此项目设置为在 Visual Studio 中运行,并配置其线程模型:

    1. 在“解决方案资源管理器”中,右键单击 GuiStep_1 项目,然后选择“设为启动项目”。

    2. 再次右键单击它,然后选择“属性”。

    3. 在项目的“属性页”对话框中:

      1. 在左上角的下拉列表中,将“配置”设置为“所有配置”。
      2. 在“配置属性”下,展开 C/C++,选择“代码生成”,并设置“运行时库”:多线程调试 (/MTd)。
  3. 将 GuiStep_1.cpp 的内容替换为以下代码:

    // GuiStep_1.cpp : Defines the entry point for the application.
    //
    
    #include "stdafx.h"
    #include "GuiStep_1.h"
    #include "..\HelloModule_en_us\resource.h"
    
    #define SUFFICIENTLY_LARGE_STRING_BUFFER (MAX_PATH*2)
    #define SUFFICIENTLY_LARGE_ERROR_BUFFER (1024*2)
    #define HELLO_MODULE_CONTRIVED_FILE_PATH  (L"HelloModule.dll")
    
    int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
    {
        UNREFERENCED_PARAMETER(hInstance);
        UNREFERENCED_PARAMETER(hPrevInstance);
        UNREFERENCED_PARAMETER(lpCmdLine);
        UNREFERENCED_PARAMETER(nCmdShow);
    
        // The following code presents a hypothetical, yet common use pattern of MUI technology
        WCHAR displayBuffer[SUFFICIENTLY_LARGE_ERROR_BUFFER];
    
        // 1. Basic application obtains access to the proper resource container 
        // for standard Win32 resource loading this is normally a PE module - use LoadLibraryEx
        // LoadLibraryEx is the preferred alternative for resource modules as used below because it
        // provides increased security and performance over that of LoadLibrary
        HMODULE resContainer = LoadLibraryExW(HELLO_MODULE_CONTRIVED_FILE_PATH,NULL,LOAD_LIBRARY_AS_IMAGE_RESOURCE | LOAD_LIBRARY_AS_DATAFILE);
        if(!resContainer)
        {
            swprintf_s(displayBuffer,SUFFICIENTLY_LARGE_ERROR_BUFFER,L"FAILURE: Unable to load the resource container module, last error = %d.",GetLastError());
            MessageBoxW(NULL,displayBuffer,L"HelloMUI ERROR!",MB_OK | MB_ICONERROR);
            return 1; // exit
        }
    
        // 2. Application parses the resource container to find the appropriate item
        WCHAR szHello[SUFFICIENTLY_LARGE_STRING_BUFFER];
        if(LoadStringW(resContainer,HELLO_MUI_STR_0,szHello,SUFFICIENTLY_LARGE_STRING_BUFFER) == 0)
        {
            swprintf_s(displayBuffer,SUFFICIENTLY_LARGE_ERROR_BUFFER,L"FAILURE: Unable to load the resource string, last error = %d.",GetLastError());
            MessageBoxW(NULL,displayBuffer,L"HelloMUI ERROR!",MB_OK | MB_ICONERROR);
            FreeLibrary(resContainer);
            return 1; // exit
        }
    
        // 3. Application presents the discovered resource to the user via UI
        swprintf_s(displayBuffer,SUFFICIENTLY_LARGE_ERROR_BUFFER,L"%s MUI",szHello);
        MessageBoxW(NULL,displayBuffer,L"HelloMUI",MB_OK | MB_ICONINFORMATION);
    
        // 4. Application cleans up memory associated with the resource container after this item is no longer needed.
        if(!FreeLibrary(resContainer))
        {
            swprintf_s(displayBuffer,SUFFICIENTLY_LARGE_ERROR_BUFFER,L"FAILURE: Unable to unload the resource container, last error = %d.",GetLastError());
            MessageBoxW(NULL,displayBuffer,L"HelloMUI ERROR!",MB_OK | MB_ICONERROR);
            return 1; // exit
        }
    
        return 0;
    }
    
  4. 生成并运行应用程序。 输出将以当前设置为计算机显示语言的语言显示(前提是它是我们生成的七种语言之一)。

步骤 4:全球化“Hello MUI”

尽管上一个示例能够以不同语言显示其输出,但它在很多方面都存在不足。 最值得关注的可能是,与 Windows 操作系统本身相比,应用程序仅在一小部分语言中可用。 例如,如果上一步中的 GuiStep_1 应用程序安装在日语版本的 Windows 上,则资源定位可能会失败。

若要解决此问题,有两个主要选项:

  • 确保包含预先确定的最终回退语言中的资源。
  • 为最终用户提供一种方法,使其能够从应用程序专门支持的语言子集中配置其语言首选项。

在这些选项中,强烈建议使用提供最终回退语言的选项,并且在本教程中通过将 -g 标志传递给 muirct.exe 来实现,如上所示。 此标志会告知 muirct.exe 将特定语言 (de-DE / 0x0407) 设置为与中性语言 dll 模块 (HelloModule.dll) 关联的最终回退语言。 在运行时,此参数用作生成显示给用户的文本的最后手段。 如果未找到最终回退语言,并且中性语言二进制文件中没有适当的资源可用,则资源加载会失败。 因此,应仔细确定应用程序可能会遇到的情况,并相应地规划最终回退语言。

另一种选择是允许可配置的语言首选项,并基于此用户定义的层次结构加载资源,这可以大大提高客户满意度。 遗憾的是,这也会使应用程序中所需的功能变得复杂。

本教程的这一步使用简化的文本文件机制来启用自定义用户语言配置。 文本文件在运行时由应用程序分析,并且经分析和验证的语言列表将用于建立自定义回退列表。 建立自定义回退列表后,Windows API 将根据此列表中设置的语言优先级加载资源。 代码的其余部分与上一步中的代码类似。

  1. 使用以下设置和值将新项目添加到 HelloMUI 解决方案(使用菜单选择“文件”、“添加”和“新建项目”):

    1. 项目类型:Win32 Project。
    2. 名称:GuiStep_2。
    3. 位置:接受默认位置。
    4. 在 Win32 应用程序向导中,选择默认应用程序类型:Windows 应用程序。
  2. 将此项目设置为在 Visual Studio 中运行,并配置其线程模型:

    1. 在“解决方案资源管理器”中,右键单击 GuiStep_2 项目,然后选择“设为启动项目”。

    2. 再次右键单击它,然后选择“属性”。

    3. 在项目的“属性页”对话框中:

      1. 在左上角的下拉列表中,将“配置”设置为“所有配置”。
      2. 在“配置属性”下,展开 C/C++,选择“代码生成”,并设置“运行时库”:多线程调试 (/MTd)。
  3. 将 GuiStep_2.cpp 的内容替换为以下代码:

    // GuiStep_2.cpp : Defines the entry point for the application.
    //
    
    #include "stdafx.h"
    #include "GuiStep_2.h"
    #include <strsafe.h>
    #include "..\HelloModule_en_us\resource.h"
    
    #define SUFFICIENTLY_LARGE_STRING_BUFFER (MAX_PATH*2)
    #define USER_CONFIGURATION_STRING_BUFFER (((LOCALE_NAME_MAX_LENGTH+1)*5)+1)
    #define SUFFICIENTLY_LARGE_ERROR_BUFFER (1024*2)
    #define HELLO_MODULE_CONTRIVED_FILE_PATH  (L"HelloModule.dll")
    
    BOOL GetMyUserDefinedLanguages(WCHAR * langStr, DWORD langStrSize);
    BOOL ConvertMyLangStrToMultiLangStr(WCHAR * langStr, WCHAR * langMultiStr, DWORD langMultiStrSize);
    
    int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
    {
        UNREFERENCED_PARAMETER(hInstance);
        UNREFERENCED_PARAMETER(hPrevInstance);
        UNREFERENCED_PARAMETER(lpCmdLine);
        UNREFERENCED_PARAMETER(nCmdShow);
    
        // The following code presents a hypothetical, yet common use pattern of MUI technology
        WCHAR displayBuffer[SUFFICIENTLY_LARGE_ERROR_BUFFER];
    
        // 1. Application starts by applying any user defined language preferences
        // (language setting is potentially optional for an application that wishes to strictly use OS system language fallback)
        // 1a. Application looks in pre-defined location for user preferences (registry, file, web, etc.)
        WCHAR userLanguagesString[USER_CONFIGURATION_STRING_BUFFER*2];
        if(!GetMyUserDefinedLanguages(userLanguagesString,USER_CONFIGURATION_STRING_BUFFER*2))
        {
            swprintf_s(displayBuffer,SUFFICIENTLY_LARGE_ERROR_BUFFER,L"FAILURE: Unable to find the user defined language configuration, last error = %d.",GetLastError());
            MessageBoxW(NULL,displayBuffer,L"HelloMUI ERROR!",MB_OK | MB_ICONERROR);
            return 1; // exit
        }
        // 1b. Application converts the user defined 'readable' languages to the proper multi-string 'less readable' language name format
        WCHAR userLanguagesMultiString[USER_CONFIGURATION_STRING_BUFFER];
        if(!ConvertMyLangStrToMultiLangStr(userLanguagesString,userLanguagesMultiString,USER_CONFIGURATION_STRING_BUFFER))
        {
            swprintf_s(displayBuffer,SUFFICIENTLY_LARGE_ERROR_BUFFER,L"FAILURE: Unable to convert the user defined language configuration to multi-string, last error = %d.",GetLastError());
            MessageBoxW(NULL,displayBuffer,L"HelloMUI ERROR!",MB_OK | MB_ICONERROR);
            return 1; // exit
        }
        // 1c. Application now sets the appropriate fallback list
        DWORD langCount = 0;
        // next commented out line of code could be used on Windows 7 and later
        // using SetProcessPreferredUILanguages is recomended for new applications (esp. multi-threaded applications)
    //    if(!SetProcessPreferredUILanguages(MUI_LANGUAGE_NAME,userLanguagesMultiString,&langCount) || langCount == 0)
        // the following line of code is supported on Windows Vista and later
        if(!SetThreadPreferredUILanguages(MUI_LANGUAGE_NAME,userLanguagesMultiString,&langCount) || langCount == 0)
        {
            swprintf_s(displayBuffer,SUFFICIENTLY_LARGE_ERROR_BUFFER,L"FAILURE: Unable to set the user defined languages, last error = %d.",GetLastError());
            MessageBoxW(NULL,displayBuffer,L"HelloMUI ERROR!",MB_OK | MB_ICONERROR);
            return 1; // exit
        }
        // NOTES on step #1:
        // an application developer that makes the assumption the fallback list provided by the
        // system / OS is entirely sufficient may or may not be making a good assumption based 
        // mostly on:
        // A. your choice of languages installed with your application
        // B. the languages on the OS at application install time
        // C. the OS users propensity to install/uninstall language packs
        // D. the OS users propensity to change laguage settings
    
        // 2. Application obtains access to the proper resource container 
        // for standard Win32 resource loading this is normally a PE module - use LoadLibraryEx
        // LoadLibraryEx is the preferred alternative for resource modules as used below because it
        // provides increased security and performance over that of LoadLibrary
        HMODULE resContainer = LoadLibraryExW(HELLO_MODULE_CONTRIVED_FILE_PATH,NULL,LOAD_LIBRARY_AS_IMAGE_RESOURCE | LOAD_LIBRARY_AS_DATAFILE);
        if(!resContainer)
        {
            swprintf_s(displayBuffer,SUFFICIENTLY_LARGE_ERROR_BUFFER,L"FAILURE: Unable to load the resource container module, last error = %d.",GetLastError());
            MessageBoxW(NULL,displayBuffer,L"HelloMUI ERROR!",MB_OK | MB_ICONERROR);
            return 1; // exit
        }
    
        // 3. Application parses the resource container to find the appropriate item
        WCHAR szHello[SUFFICIENTLY_LARGE_STRING_BUFFER];
        if(LoadStringW(resContainer,HELLO_MUI_STR_0,szHello,SUFFICIENTLY_LARGE_STRING_BUFFER) == 0)
        {
            swprintf_s(displayBuffer,SUFFICIENTLY_LARGE_ERROR_BUFFER,L"FAILURE: Unable to load the resource string, last error = %d.",GetLastError());
            MessageBoxW(NULL,displayBuffer,L"HelloMUI ERROR!",MB_OK | MB_ICONERROR);
            FreeLibrary(resContainer);
            return 1; // exit
        }
    
        // 4. Application presents the discovered resource to the user via UI
        swprintf_s(displayBuffer,SUFFICIENTLY_LARGE_ERROR_BUFFER,L"%s MUI",szHello);
        MessageBoxW(NULL,displayBuffer,L"HelloMUI",MB_OK | MB_ICONINFORMATION);
    
        // 5. Application cleans up memory associated with the resource container after this item is no longer needed.
        if(!FreeLibrary(resContainer))
        {
            swprintf_s(displayBuffer,SUFFICIENTLY_LARGE_ERROR_BUFFER,L"FAILURE: Unable to unload the resource container, last error = %d.",GetLastError());
            MessageBoxW(NULL,displayBuffer,L"HelloMUI ERROR!",MB_OK | MB_ICONERROR);
            return 1; // exit
        }
    
        return 0;
    }
    
    BOOL GetMyUserDefinedLanguages(WCHAR * langStr, DWORD langStrSize)
    {
        BOOL rtnVal = FALSE;
        // very simple implementation - assumes that first 'langStrSize' characters of the 
        // L".\\langs.txt" file comprises a string of one or more languages
        HANDLE langConfigFileHandle = CreateFileW(L".\\langs.txt", GENERIC_READ, 0, 
            NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
        if(langConfigFileHandle != INVALID_HANDLE_VALUE)
        {
            // clear out the input variables
            DWORD bytesActuallyRead = 0;
            if(ReadFile(langConfigFileHandle,langStr,langStrSize*sizeof(WCHAR),&bytesActuallyRead,NULL) && bytesActuallyRead > 0)
            {
                rtnVal = TRUE;
                DWORD nullIndex = (bytesActuallyRead/sizeof(WCHAR) < langStrSize) ? bytesActuallyRead/sizeof(WCHAR) : langStrSize;
                langStr[nullIndex] = L'\0';
            }
            CloseHandle(langConfigFileHandle);
        }
        return rtnVal;
    }
    BOOL ConvertMyLangStrToMultiLangStr(WCHAR * langStr, WCHAR * langMultiStr, DWORD langMultiStrSize)
    {
        BOOL rtnVal = FALSE;
        size_t strLen = 0;
        rtnVal = SUCCEEDED(StringCchLengthW(langStr,USER_CONFIGURATION_STRING_BUFFER*2,&strLen));
        if(rtnVal && strLen > 0 && langMultiStr && langMultiStrSize > 0)
        {
            WCHAR * langMultiStrPtr = langMultiStr;
            WCHAR * last = langStr + (langStr[0] == 0xFEFF ? 1 : 0);
            WCHAR * context = last;
            WCHAR * next = wcstok_s(last,L",; :",&context);
            while(next && rtnVal)
            {
                // make sure you validate the user input
                if(SUCCEEDED(StringCchLengthW(last,LOCALE_NAME_MAX_LENGTH,&strLen)) && 
                    IsValidLocaleName(next))
                {
                    langMultiStrPtr[0] = L'\0';
                    rtnVal &= SUCCEEDED(StringCchCatW(langMultiStrPtr,(langMultiStrSize - (langMultiStrPtr - langMultiStr)),next));
                    langMultiStrPtr += strLen + 1;
                }
                next = wcstok_s(NULL,L",; :",&context);
                if(next)
                    last = next;
            }
            if(rtnVal && (langMultiStrSize - (langMultiStrPtr - langMultiStr))) // make sure there is a double null term for the multi-string
            {
                langMultiStrPtr[0] = L'\0';
            }
            else // fail and guard anyone whom might use the multi-string
            {
                langMultiStr[0] = L'\0';
                langMultiStr[1] = L'\0';
            }
        }
        return rtnVal;
    }
    
  4. 创建包含以下行的 Unicode 文本文件 langs.txt:

    hi-IN ta-IN ru-RU fr-FR es-ES en-US
    

    注意

    请务必将文件另存为 Unicode。

     

    将 langs.txt 复制到程序的运行目录:

    • 如果要从 Visual Studio 中运行,请将其复制到 ProjectRootDirectory\HelloMUI\GuiStep_2。
    • 如果要从 Windows 资源管理器运行,请将其复制到与 GuiStep_2.exe 相同的目录。
  5. 生成并运行该项目。 尝试编辑 langs.txt,让不同的语言显示在列表的前面。

步骤 5:自定义“Hello MUI”

在本教程中目前提及的一些运行时功能仅在 Windows Vista 及更高版本中可用。 你可能希望将应用程序用于下层 Windows 操作系统版本(如 Windows XP)来重新利用在资源的本地化和拆分方面取得的成果。 此过程需要在两个关键领域对上一个示例进行调整:

  • Windows Vista 之前版本的资源加载函数(如 LoadStringLoadIconLoadBitmapFormatMessage 等)不能识别 MUI。 附带拆分资源(LN 和 .mui 文件)的应用程序必须使用以下两个函数之一加载资源模块:

    • 如果应用程序仅在 Windows Vista 及更高版本上运行,则应使用 LoadLibraryEx 加载资源模块。
    • 如果应用程序要在 Windows Vista 之前的版本以及 Windows Vista 或更高版本上运行,则必须使用 LoadMUILibrary,它是 Windows 7 SDK 中提供的特定下层函数。
  • 在 Windows Vista 之前的 Windows 操作系统版本中提供的语言管理和语言回退顺序支持明显不同于 Windows Vista 及更高版本中的语言管理和语言回退顺序。 因此,支持用户配置语言回退的应用程序必须调整其语言管理做法:

    • 如果应用程序仅在 Windows Vista 及更高版本上运行,则使用 SetThreadPreferredUILanguages 设置语言列表就足够了。
    • 如果应用程序要在所有 Windows 版本上运行,则必须构造将在下层平台上运行的代码,以循环访问用户配置的语言列表并探测所需资源模块。 此步骤后面代码的第 1c 和 2 节中显示了这一点。

创建可在任何版本的 Windows 上使用本地化资源模块的项目:

  1. 使用以下设置和值将新项目添加到 HelloMUI 解决方案(使用菜单选择“文件”、“添加”和“新建项目”):

    1. 项目类型:Win32 Project。
    2. 名称:GuiStep_3。
    3. 位置:接受默认位置。
    4. 在 Win32 应用程序向导中,选择默认应用程序类型:Windows 应用程序。
  2. 将此项目设置为在 Visual Studio 中运行,并配置其线程模型。 此外,请将其配置为添加必要的标头和库。

    注意

    本教程中使用的路径假定 Windows 7 SDK 和 Microsoft NLS 下层 API 包已安装到各自的默认目录。 如果不是这种情况,请相应地修改路径。

     

    1. 在“解决方案资源管理器”中,右键单击 GuiStep_3 项目,然后选择“设为启动项目”。

    2. 再次右键单击它,然后选择“属性”。

    3. 在项目的“属性页”对话框中:

      1. 在左上角的下拉列表中,将“配置”设置为“所有配置”。

      2. 在“配置属性”下,展开 C/C++,选择“代码生成”,并设置“运行时库”:多线程调试 (/MTd)。

      3. 选择“常规”并添加到“附加包含目录”:

        • “C:\Microsoft NLS Downlevel APIs\Include”。
      4. 选择“语言”并设置“将 wchar_t 视为内置类型”:否 (/Zc:wchar_t-)。

      5. 选择“高级”并设置通话约定:_stdcall (/Gz)。

      6. 在“配置属性”下,展开“链接器”,选择“输入”,然后添加到“其他依赖项”:

        • “C:\Program Files\Microsoft SDKs\Windows\v7.0\Lib\MUILoad.lib”。
        • “C:\Microsoft NLS Downlevel APIs\Lib\x86\Nlsdl.lib”。
  3. 将 GuiStep_3.cpp 的内容替换为以下代码:

    // GuiStep_3.cpp : Defines the entry point for the application.
    //
    
    #include "stdafx.h"
    #include "GuiStep_3.h"
    #include <strsafe.h>
    #include <Nlsdl.h>
    #include <MUILoad.h>
    #include "..\HelloModule_en_us\resource.h"
    
    #define SUFFICIENTLY_LARGE_STRING_BUFFER (MAX_PATH*2)
    #define USER_CONFIGURATION_STRING_BUFFER (((LOCALE_NAME_MAX_LENGTH+1)*5)+1)
    #define SUFFICIENTLY_LARGE_ERROR_BUFFER (1024*2)
    #define HELLO_MODULE_CONTRIVED_FILE_PATH  (L"HelloModule.dll")
    
    BOOL GetMyUserDefinedLanguages(WCHAR * langStr, DWORD langStrSize);
    BOOL ConvertMyLangStrToMultiLangStr(WCHAR * langStr, WCHAR * langMultiStr, DWORD langMultiStrSize);
    
    int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
    {
        UNREFERENCED_PARAMETER(hInstance);
        UNREFERENCED_PARAMETER(hPrevInstance);
        UNREFERENCED_PARAMETER(lpCmdLine);
        UNREFERENCED_PARAMETER(nCmdShow);
    
        // The following code presents a hypothetical, yet common use pattern of MUI technology
        WCHAR displayBuffer[SUFFICIENTLY_LARGE_ERROR_BUFFER];
    
        // 1. Application starts by applying any user defined language preferences
        // (language setting is potentially optional for an application that wishes to strictly use OS system language fallback)
        // 1a. Application looks in pre-defined location for user preferences (registry, file, web, etc.)
        WCHAR userLanguagesString[USER_CONFIGURATION_STRING_BUFFER*2];
        if(!GetMyUserDefinedLanguages(userLanguagesString,USER_CONFIGURATION_STRING_BUFFER*2))
        {
            swprintf_s(displayBuffer,SUFFICIENTLY_LARGE_ERROR_BUFFER,L"FAILURE: Unable to find the user defined language configuration, last error = %d.",GetLastError());
            MessageBoxW(NULL,displayBuffer,L"HelloMUI ERROR!",MB_OK | MB_ICONERROR);
            return 1; // exit
        }
        // 1b. Application converts the user defined 'readable' languages to the proper multi-string 'less readable' language name format
        WCHAR userLanguagesMultiString[USER_CONFIGURATION_STRING_BUFFER];
        if(!ConvertMyLangStrToMultiLangStr(userLanguagesString,userLanguagesMultiString,USER_CONFIGURATION_STRING_BUFFER))
        {
            swprintf_s(displayBuffer,SUFFICIENTLY_LARGE_ERROR_BUFFER,L"FAILURE: Unable to convert the user defined language configuration to multi-string, last error = %d.",GetLastError());
            MessageBoxW(NULL,displayBuffer,L"HelloMUI ERROR!",MB_OK | MB_ICONERROR);
            return 1; // exit
        }
        // 1c. Application now attempts to set the fallback list - this is much different for a down-level 
        // shipping application when compared to a Windows Vista or Windows 7 only shipping application    
        BOOL setSuccess = FALSE;
        DWORD setLangCount = 0;
        HMODULE hDLL = GetModuleHandleW(L"kernel32.dll");
        if( hDLL )
        {
            typedef BOOL (* SET_PREFERRED_UI_LANGUAGES_PROTOTYPE ) ( DWORD, PCWSTR, PULONG );
            SET_PREFERRED_UI_LANGUAGES_PROTOTYPE fp_SetPreferredUILanguages = (SET_PREFERRED_UI_LANGUAGES_PROTOTYPE)NULL;
            fp_SetPreferredUILanguages = (SET_PREFERRED_UI_LANGUAGES_PROTOTYPE) GetProcAddress(hDLL,"SetProcessPreferredUILanguages");
            if( fp_SetPreferredUILanguages )
            {
                // call SetProcessPreferredUILanguages if it is available in Kernel32.dll's export table - Windows 7 and later
                setSuccess = fp_SetPreferredUILanguages(MUI_LANGUAGE_NAME,userLanguagesMultiString,&setLangCount);
            }
            else
            {
                fp_SetPreferredUILanguages = (SET_PREFERRED_UI_LANGUAGES_PROTOTYPE) GetProcAddress(hDLL,"SetThreadPreferredUILanguages");
                // call SetThreadPreferredUILanguages if it is available in Kernel32.dll's export table - Windows Vista and later
                if(fp_SetPreferredUILanguages)
                    setSuccess = fp_SetPreferredUILanguages(MUI_LANGUAGE_NAME,userLanguagesMultiString,&setLangCount);
            }
        }
    
        // 2. Application obtains access to the proper resource container 
        // for standard Win32 resource loading this is normally a PE module
        // LoadMUILibrary is the preferred alternative for loading of resource modules
        // when the application is potentially run on OS versions prior to Windows Vista
        // LoadMUILibrary is available via Windows SDK releases in Windows Vista and later
        // When available, it is advised to get the most up-to-date Windows SDK (e.g., Windows 7)
        HMODULE resContainer = NULL;
        if(setSuccess) // Windows Vista and later OS scenario
        {
            resContainer = LoadMUILibraryW(HELLO_MODULE_CONTRIVED_FILE_PATH,MUI_LANGUAGE_NAME,0);
        }
        else // this block should only be hit on Windows XP and earlier OS platforms as setSuccess will be TRUE on Windows Vista and later
        {
            // need to provide your own fallback mechanism such as the implementation below
            // in essence the application will iterate through the user configured language list
            WCHAR * next = userLanguagesMultiString;
            while(!resContainer && *next != L'\0')
            {
                // convert the language name to an appropriate LCID
                // DownlevelLocaleNameToLCID is available via standalone download package 
                // and is contained in Nlsdl.h / Nlsdl.lib
                LCID nextLcid = DownlevelLocaleNameToLCID(next,DOWNLEVEL_LOCALE_NAME);
                // then have LoadMUILibrary attempt to probe for the right .mui module
                resContainer = LoadMUILibraryW(HELLO_MODULE_CONTRIVED_FILE_PATH,MUI_LANGUAGE_NAME,(LANGID)nextLcid);
                // increment to the next language name in our list
                size_t nextStrLen = 0;
                if(SUCCEEDED(StringCchLengthW(next,LOCALE_NAME_MAX_LENGTH,&nextStrLen)))
                    next += (nextStrLen + 1);
                else
                    break; // string is invalid - need to exit
            }
            // if the user configured list did not locate a module then try the languages associated with the system
            if(!resContainer)
                resContainer = LoadMUILibraryW(HELLO_MODULE_CONTRIVED_FILE_PATH,MUI_LANGUAGE_NAME,0);
        }
        if(!resContainer)
        {
            swprintf_s(displayBuffer,SUFFICIENTLY_LARGE_ERROR_BUFFER,L"FAILURE: Unable to load the resource container module, last error = %d.",GetLastError());
            MessageBoxW(NULL,displayBuffer,L"HelloMUI ERROR!",MB_OK | MB_ICONERROR);
            return 1; // exit
        }
    
        // 3. Application parses the resource container to find the appropriate item
        WCHAR szHello[SUFFICIENTLY_LARGE_STRING_BUFFER];
        if(LoadStringW(resContainer,HELLO_MUI_STR_0,szHello,SUFFICIENTLY_LARGE_STRING_BUFFER) == 0)
        {
            swprintf_s(displayBuffer,SUFFICIENTLY_LARGE_ERROR_BUFFER,L"FAILURE: Unable to load the resource string, last error = %d.",GetLastError());
            MessageBoxW(NULL,displayBuffer,L"HelloMUI ERROR!",MB_OK | MB_ICONERROR);
            FreeLibrary(resContainer);
            return 1; // exit
        }
    
        // 4. Application presents the discovered resource to the user via UI
        swprintf_s(displayBuffer,SUFFICIENTLY_LARGE_ERROR_BUFFER,L"%s MUI",szHello);
        MessageBoxW(NULL,displayBuffer,L"HelloMUI",MB_OK | MB_ICONINFORMATION);
    
        // 5. Application cleans up memory associated with the resource container after this item is no longer needed.
        if(!FreeMUILibrary(resContainer))
        {
            swprintf_s(displayBuffer,SUFFICIENTLY_LARGE_ERROR_BUFFER,L"FAILURE: Unable to unload the resource container, last error = %d.",GetLastError());
            MessageBoxW(NULL,displayBuffer,L"HelloMUI ERROR!",MB_OK | MB_ICONERROR);
            return 1; // exit
        }
    
        return 0;
    }
    
    BOOL GetMyUserDefinedLanguages(WCHAR * langStr, DWORD langStrSize)
    {
        BOOL rtnVal = FALSE;
        // very simple implementation - assumes that first 'langStrSize' characters of the 
        // L".\\langs.txt" file comprises a string of one or more languages
        HANDLE langConfigFileHandle = CreateFileW(L".\\langs.txt", GENERIC_READ, 0, 
            NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
        if(langConfigFileHandle != INVALID_HANDLE_VALUE)
        {
            // clear out the input variables
            DWORD bytesActuallyRead = 0;
            if(ReadFile(langConfigFileHandle,langStr,langStrSize*sizeof(WCHAR),&bytesActuallyRead,NULL) && bytesActuallyRead > 0)
            {
                rtnVal = TRUE;
                DWORD nullIndex = (bytesActuallyRead/sizeof(WCHAR) < langStrSize) ? bytesActuallyRead/sizeof(WCHAR) : langStrSize;
                langStr[nullIndex] = L'\0';
            }
            CloseHandle(langConfigFileHandle);
        }
        return rtnVal;
    }
    BOOL ConvertMyLangStrToMultiLangStr(WCHAR * langStr, WCHAR * langMultiStr, DWORD langMultiStrSize)
    {
        BOOL rtnVal = FALSE;
        size_t strLen = 0;
        rtnVal = SUCCEEDED(StringCchLengthW(langStr,USER_CONFIGURATION_STRING_BUFFER*2,&strLen));
        if(rtnVal && strLen > 0 && langMultiStr && langMultiStrSize > 0)
        {
            WCHAR * langMultiStrPtr = langMultiStr;
            WCHAR * last = langStr + (langStr[0] == 0xFEFF ? 1 : 0);
            WCHAR * context = last;
            WCHAR * next = wcstok_s(last,L",; :",&context);
            while(next && rtnVal)
            {
                // make sure you validate the user input
                if(SUCCEEDED(StringCchLengthW(last,LOCALE_NAME_MAX_LENGTH,&strLen)) 
                    && DownlevelLocaleNameToLCID(next,0) != 0)
                {
                    langMultiStrPtr[0] = L'\0';
                    rtnVal &= SUCCEEDED(StringCchCatW(langMultiStrPtr,(langMultiStrSize - (langMultiStrPtr - langMultiStr)),next));
                    langMultiStrPtr += strLen + 1;
                }
                next = wcstok_s(NULL,L",; :",&context);
                if(next)
                    last = next;
            }
            if(rtnVal && (langMultiStrSize - (langMultiStrPtr - langMultiStr))) // make sure there is a double null term for the multi-string 
            {
                langMultiStrPtr[0] = L'\0';
            }
            else // fail and guard anyone whom might use the multi-string
            {
                langMultiStr[0] = L'\0';
                langMultiStr[1] = L'\0';
            }
        }
        return rtnVal;
    }
    
  4. 创建 langs.txt 或将其复制到适当的目录,如前面的步骤 4:全球化“Hello MUI”中所述。

  5. 生成并运行该项目。

注意

如果应用程序应在 Windows Vista 之前的 Windows 版本上运行,请务必阅读 Microsoft NLS 下层 API 包附带的文档,了解如何重新分发 Nlsdl.dll。 (Microsoft 下载中心不再提供该包。在 Windows 10 2019 年 5 月更新及更高版本上使用 ICU 全球化 API。)

 

MUI 的其他注意事项

对控制台应用程序的支持

本教程中介绍的技术也可以在控制台应用程序中使用。 但是,与大多数标准 GUI 控件不同,Windows 命令窗口无法显示所有语言的字符。 因此,需要特别注意多语言控制台应用程序。

调用具有特定筛选标志的 API SetThreadUILanguageSetThreadPreferredUILanguage 会导致资源加载函数删除通常不在命令窗口中显示的特定语言的语言资源探测。 设置这些标志时,语言设置算法仅允许那些将在命令窗口中正确显示的语言出现在回退列表中。

有关使用这些 API 生成多语言控制台应用程序的详细信息,请参阅 SetThreadUILanguageSetThreadPreferredUILanguages 的备注部分。

确定运行时支持的语言

可以采用以下设计建议之一来确定应用程序在运行时应支持的语言:

  • 在安装过程中,使最终用户能够从支持的语言列表中选择首选语言

  • 从配置文件读取语言列表

    本教程中的某些项目包含一个用于分析 langs.txt 配置文件(包含语言列表)的函数。

    由于此函数采用外部输入,因此请务必验证作为输入提供的语言。 有关执行该验证的更多详细信息,请参阅 IsValidLocaleNameDownLevelLocaleNameToLCID 函数。

  • 查询操作系统以确定安装了哪些语言

    此方法可使应用程序使用与操作系统相同的语言。 尽管这不需要用户提示,但如果选择此选项,请注意操作系统语言可以随时添加或删除,并且可以在用户安装应用程序后进行更改。 此外,请注意,在某些情况下,所安装操作系统的语言支持是有限的,如果应用程序支持操作系统不支持的语言,则应用程序会提供更多价值。

    有关如何确定操作系统中当前安装的语言的详细信息,请参阅 EnumUILanguages 函数。

对 Windows Vista 之前版本中的复杂脚本的支持

当支持某些复杂脚本的应用程序在 Windows Vista 之前的 Windows 版本上运行时,该脚本中的文本可能无法在 GUI 组件中正确显示。 例如,在本教程中的下层项目中,由于处理复杂脚本的问题和缺乏相关字体,hi-IN 和 ta-IN 脚本可能不会显示在消息框中。 通常,这种性质的问题在 GUI 组件中显示为方框。

有关如何实现复杂脚本处理的详细信息,请参阅 Windows 中的脚本和字体支持

总结

本教程对单语应用程序进行了全球化,并演示了以下最佳做法。

  • 设计应用程序以利用 Win32 资源模型。
  • 利用 MUI 将资源拆分为附属二进制文件(.mui 文件)。
  • 确保本地化过程将更新 .mui 文件中的资源,以适合目标语言的要求。
  • 确保正确打包和部署应用程序、关联的 .mui 文件和配置内容,以允许资源加载 API 找到本地化内容。
  • 为最终用户提供一种调整应用程序语言配置的机制。
  • 调整运行时代码以利用语言配置,使应用程序更好地响应最终用户的需求。