共用方式為


將 多語系使用者介面 支援新增至應用程式

本教學課程示範如何採用單一語種應用程式,並將其設為世界就緒。 此應用程式的形式是 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 SDK for Windows 7。

    您可以從 Windows 開發人員中心的 Windows SDK 頁面進行安裝。 SDK 包含公用程式,可針對從 Windows XP 到最新的作業系統版本開發應用程式的公用程式。

    注意

    如果您未將套件安裝到預設位置,或未安裝在系統磁碟驅動器上,通常是 C 磁碟驅動器,請記下安裝路徑。

     

  3. 設定 Visual Studio 命令行參數。

    1. 開啟 Visual Studio 命令視窗。
    2. 類型 設定路徑
    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 資源相關層面,我們會將運行時間 DLL 程式代碼保留在 dllmain.cpp 中。 本教學課程的後續章節會利用這裡所建置的 HelloModule 資源數據,並呈現適當的運行時間程式代碼。

若要建構 Win32 資源模組,請從建立具有 stubbed out dllmain 的 DLL 開始:

  1. 將新專案新增至 HelloMUI 方案:

    1. 從 [檔案] 功能表中,選取 [新增],然後選取 [新增專案]。
    2. 專案類型:Win32 專案。
    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. 標識碼:HELLO_MUI_STR_0。
        2. 值:0。
        3. 標題:Hello。

      如果您現在以文字檢視 HelloModule.rc,您會看到各種資源特定的原始程式碼片段。 最感興趣的一個是描述 「Hello」 字串的區段:

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

      這個 「Hello」 字串是需要本地化的資源,也就是翻譯為應用程式所希望支援的每個語言。 例如,HelloModule_ta_in專案(將在下一個步驟中建立的專案)將包含其本身的 HelloModule.rc 版本,適用於 “ta-IN”:

      /////////////////////////////////////////////////////////////////////////////
      //
      // String Table
      //
      
      STRINGTABLE 
      BEGIN
          HELLO_MUI_STR_0         "வணக்கம்"
      END
      
    5. 建置HelloModule_en_us專案以確定沒有任何錯誤。

  5. 在 HelloMUI 解決方案中建立六個專案(或您想要的其中多個專案),為其他語言再建立六個資源 DLL。 使用下表中的值:

    專案名稱 .rc 檔案的名稱 字串識別碼 [字串值] 字串 標題
    HelloModule_de_de HelloModule HELLO_MUI_STR_0 0
    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 旗標來指定目標語言標識符。 提供給 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:建立 Resource-Savvy “Hello MUI”

根據上述的初始硬式編碼GuiStep_0.exe 範例,您可以選擇納入 Win32 資源模型,將應用程式的觸達範圍延伸至多種語言使用者。 此步驟中呈現的新運行時間程式代碼包含模組載入 (LoadLibraryEx) 和字串擷取 (LoadString) 邏輯。

  1. 使用下列設定和值,將新專案新增至 HelloMUI 方案(使用功能表選取 [檔案]、[新增] 和 [新增專案] :

    1. 專案類型:Win32 專案。
    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 專案。
    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 前資源載入函式(例如 LoadStringLoadIcon、LoadBitmapFormatMessage 等)是非 MUI 感知的。 隨附分割資源的應用程式(LN 和 .mui 檔案)必須使用下列兩個函式之一載載資源模組:

    • 如果應用程式只在 Windows Vista 和更新版本上執行,則應該使用 LoadLibraryEx 載入資源模組。
    • 如果應用程式必須在 Windows Vista 之前的版本以及 Windows Vista 或更新版本上執行,則必須使用 LoadMUILibrary,這是 Windows 7 SDK 中提供的特定下層函式。
  • Windows 作業系統前版本所提供的語言管理和語言後援順序支援,與 Windows Vista 和更新版本中的語言管理與語言後援順序明顯不同。 因此,允許使用者設定語言後援的應用程式必須調整其語言管理做法:

    • 如果應用程式只在 Windows Vista 和更新版本上執行,則使用 SetThreadPreferredUILanguages 設定語言清單就已足夠。
    • 如果要在所有 Windows 版本上執行應用程式,則必須建構程式代碼,以在舊版平台上執行,以逐一查看使用者設定的語言清單,並探查所需的資源模組。 這可以在此步驟稍後提供的程序代碼第 1c 和 2 節中看到。

建立可在任何 Windows 版本上使用當地語系化資源模組的專案:

  1. 使用下列設定和值,將新專案新增至 HelloMUI 方案(使用功能表選取 [檔案]、[新增] 和 [新增專案] :

    1. 專案類型:Win32 專案。
    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 API\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 API\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 SetThreadUILanguage 或 SetThreadPreferredUILanguages 會導致資源載入函式移除通常未顯示在命令視窗中之特定語言的語言資源探查。 設定這些旗標時,語言設定演算法只允許在後援清單中正確顯示的語言。

如需使用這些 API 建置多語系控制台應用程式的詳細資訊,請參閱 SetThreadUILanguage 和 SetThreadPreferredUILanguages備註小節。

判斷運行時間支持的語言

您可以採用下列其中一項設計建議,以判斷應用程式在執行時間應該支援的語言:

  • 在安裝期間,讓使用者從支援的語言清單中選取慣用的語言

  • 從組態檔讀取語言清單

    本教學課程中的部分專案包含用來剖析 langs.txt 組態檔的函式,其中包含語言清單。

    由於此函式接受外部輸入,因此請驗證提供做為輸入的語言。 如需執行該驗證的詳細資訊,請參閱 IsValidLocaleNameDownLevelLocaleNameToLCID 函式。

  • 查詢作業系統以判斷已安裝哪些語言

    此方法可協助應用程式使用與作業系統相同的語言。 雖然這不需要使用者提示,但如果您選擇此選項,請注意操作系統語言可以隨時新增或移除,而且可以在使用者安裝應用程式之後變更。 此外,請注意,在某些情況下,操作系統會以有限的語言支援來安裝,而且如果應用程式支援操作系統不支援的語言,則會提供更多價值。

    如需有關如何判斷操作系統中目前已安裝語言的詳細資訊,請參閱 EnumUILanguages 函 式。

Windows Vista 之前版本的複雜腳本支援

當支援特定複雜腳本的應用程式在 Windows Vista 之前的 Windows 版本上執行時,該腳本中的文字可能無法在 GUI 元件中正確顯示。 例如,在本教學課程的下層專案中,hi-IN 和 ta-IN 腳本可能無法顯示在消息框中,因為處理複雜腳本和缺少相關字型的問題。 通常,這種性質的問題會以 GUI 元件中的方塊的形式呈現。

如需如何啟用複雜腳本處理的詳細資訊,請參閱 Windows 中的腳本和字型支援。

摘要

本教學課程將單一語種應用程式全球化,並示範下列最佳做法。

  • 設計應用程式以利用 Win32 資源模型。
  • 利用 MUI 將資源分割成附屬二進位檔(.mui 檔案)。
  • 請確定本地化程式會更新 .mui 檔案中的資源,以適合目標語言。
  • 請確定已正確封裝和部署應用程式、相關聯的 .mui 檔案和組態內容,以允許資源載入 API 尋找本地化的內容。
  • 為使用者提供調整應用程式語言組態的機制。
  • 調整運行時間程式代碼以利用語言設定,讓應用程式更能回應使用者的需求。