首页 | IT新闻 | 硬件 | 操作系统 | 开发 | 网络编程 | 数据库 | 热门框架 | 网络安全 | 组网 | 建站指南 | 网页制作 | 特效 | 实用技巧 | 服务器 | 办公 | QQ | 探索 | 社区

  • 技术部落
  • 部落首页 > 程序开发 > C/C#/C++ > 正文
  • 资源DLL与语言选择菜单的实现
      2007-8-2  来源:天极Yesky  编辑:Jsbulo  热度:

      简介

      在当今这个发展越来越快的世界中,软件的本地化及翻译工作越来越重要,极大地关系到软件的销量及普及率;就拿常见的Win32/MFC程序来说,一个比较方便的办法就是附加单独的资源DLL文件。

      本文介绍了一种易于应用的方法,可在C++/MFC程序中支持多种语言,并演示了怎样用少量的代码添加对资源DLL的支持,这包含了两个方面:根据用户偏好在程序开始时自动选择最合适的语言;提供一个语言选择子菜单(以供用户自行选择)。如图所示:

      关于资源DLL

      有关在程序中支持多语言最灵活的方法,也许就是使用所谓的“资源DLL”了,其主要思想是为每种语言创建一个单独的DLL文件,而这个DLL中包含了已翻译为某种特定语言的程序资源;因此,如果你的程序最初版本是中文,且又翻译成了法文、德文、日文,这样你就有了三个资源DLL。中文资源在.exe文件中,而其他三种语言各自对应一个DLL文件。

      如果程序中又需要支持某种新的语言,只须简单地添加一个DLL到安装文件中就行了。在程序运行时,会根据用户偏好,相应地加载资源DLL。

      资源DLL可由一个专门的Visual Studio工程来创建,也可由某些专用的工具来创建,本文以Visual Studio 2003来创建,Visual Studio 2005也差不多。顺便提一下,把所有的语言资源都打包进EXE文件,在理论上是可行的,但在实际中却行不通;因为,大多数加载资源的高层API——如LoadString()、DialogBox()等等——不会让你指定想要的语言,而SetThreadLocale()也不会如预期那样工作(此API在Win9X中不存在)。

      一步一步支持资源DLL

      以下是在主程序中,添加支持资源DLL(语言选择菜单)的步骤:

      1、 把LanguageSupport.h及LanguageSupport.cpp添加到你的工程。

      在MyApp.h及MyApp.cpp(假定CMyApp是工程类)中,加入以下黑体行:

    以下是引用片段:
    #include "LanguageSupport.h"
      class CMyApp : public CWinApp
      {
      public:
      CLanguageSupport m_LanguageSupport;
      ...
      };
      BOOL CMyApp::InitInstance()
      {
      //把以下这行注释掉,防止MFC进行自己的资源DLL处理。
      // CWinApp::InitInstance();
      ...
      SetRegistryKey(_T("MyCompany"));
      //根据用户偏好,加载相应的资源DLL。
      m_LanguageSupport.LoadBestLanguage();
      ...
      }

      在主菜单中,添加一个名为“语言(Language)”的菜单项。接下来,在CMainFrame类中,为“语言(Language)”菜单项添加一个菜单更新处理程序,假定名为OnUpdateToolsLanguage(),如下所示:

    以下是引用片段:
    void CMainFrame::OnUpdateToolsLanguage(CCmdUI *pCmdUI)
      {
      //创建语言子菜单(只在菜单第一次打开时)。
      theApp.m_LanguageSupport.CreateMenu(pCmdUI);
      }

      2、 为语言选择菜单项添加菜单处理程序。

      在MainFrm.h中,把处理程序afx_msg void OnLanguage(UINT nID)添加在CMainFrame的protected部分中某处;在MainFrm.cpp中,添加定义及消息映射入口:

    以下是引用片段:
    BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
      ...
      ON_COMMAND_RANGE(ID_LANGUAGE_FIRST,ID_LANGUAGE_LAST, OnLanguage)
      //这些ID声明在LanguageSupport.h中
      END_MESSAGE_MAP()
      void CMainFrame::OnLanguage(UINT nID)
      { //用户选择了菜单中的某种语言
      theApp.m_LanguageSupport.OnSwitchLanguage(nID);
      }

      注意,这个处理程序不能用向导来添加,因为它是一个COMMAND_RANGE菜单处理程序——用于处理“语言(Language)”菜单中所有语言项。

      3、 也是最后一步,在字符资源表中,添加一个名为IDS_RESTART的字符串,值为“请重新运行%1”,%1可为程序名。

      怎样创建资源DLL

      首先,CLanguageSupport类假定所有的DLL都名为MyAppXXX.dll,这里MyApp.exe是可执行文件名,XXX是所支持语言的三个字母缩写(CHN代表中文、FRA代表法文、DEU代表德文、JPN代表日文)。同时,exe文件与dll文件都应有一个Version版本信息资源,其语言匹配文件名中的三个缩写字母。接着,我们创建一个Win32 DLL工程:

      1、 打开Visual Studio 2003,选择文件-新建-工程,输入工程名如MyAppDEU创建一个德文版本,单击确定;在程序设置页,选择DLL及空项目。

      2、 把它转换为一个资源DLL:打开项目属性,在“配置”下拉框中选择“所有配置”;打开链接器-高级,将“纯资源DLL”设为“是”。另外,如果你使用Visual C++ 6.0,则需要手工在链接设置的编辑框中添加 /NOENTRY作为命令行选项;而Visual C++ 2005,与Visual C++ 2003类似,在链接器-高级里,选择“无入口点”为“是”。

      3、 创建一份EXE资源文件的副本,并把它添加到DLL工程中:建议将MyApp.rc文件改名成所需的语言版本,如MyAppDEU.rc。

      4、 修改路径:在资源视图中,鼠标右键单击MyAppDEU.rc,打开“资源包含”,修改所有包含资源文件的路径。每种语言都有会一个子目录l.xxx,例如,修改#include "afxres.rc"为#include "l.deu\afxres.rc"。

      5、 设置语言属性:在资源视图中,打开“版本信息version info”(如果没有就创建一个),设置语言属性为正确的语言,如German(Germany),确保语言匹配DLL名中的三个字母。

      现在可以编译DLL了,之后便可得到一个资源DLL,当然,它还没有经过翻译,但这已是翻译者的任务了。按照上述步骤就可创建出一系统语言的DLL,你唯一要做的事情,就是把它们复制到应用程序的目录了。另外,从程序中加载资源DLL也非常简单,LoadLibrary()与AfxSetResourceHandle(hDll)就可以胜任了。

      以下是源代码:

    以下是引用片段:
    LanguageSupport.h
      #pragma once
      #include 
      #define ID_LANGUAGE_FIRST 0x6F00
      #define ID_LANGUAGE_LAST 0x6FFF
      class CLanguageSupport
      {
      public:
      CLanguageSupport();
      ~CLanguageSupport();
      void CreateMenu(CCmdUI *pCmdUI, UINT nFirstItemId= ID_LANGUAGE_FIRST);
      void OnSwitchLanguage(UINT nId, bool bLoadNewLanguageImmediately= false);
      void LoadBestLanguage();
      protected:
      CWordArray m_aLanguages;
      LANGID m_nExeLanguage;
      LANGID m_nCurrentLanguage;
      HINSTANCE m_hDll;
      static const TCHAR szLanguageId[];
      static LANGID GetLangIdFromFile(LPCTSTR pszFilename);
      static CString GetLanguageName(LANGID wLangId);
      static LANGID GetUserUILanguage();
      static LANGID GetSystemUILanguage();
      void GetAvailableLanguages();
      bool LoadLanguage();
      bool LoadLanguageDll();
      void LookupExeLanguage();
      void UnloadResourceDll();
      static void SetResourceHandle(HINSTANCE hDll);
      };
      LanguageSupport.cpp
      #include "stdafx.h"
      #include "resource.h"
      #include "LanguageSupport.h"
      #include 
      #ifdef _DEBUG
      #define new DEBUG_NEW
      #undef THIS_FILE
      static char THIS_FILE[] = __FILE__;
      #endif
      #pragma comment(lib, "shlwapi.lib")
      #pragma comment(lib, "version.lib")
      const TCHAR CLanguageSupport::szLanguageId[]=_T("LanguageId");
      #ifndef countof
      #define countof(x) (sizeof(x)/sizeof((x)[0]))
      #endif
      void CLanguageSupport::LoadBestLanguage()
      {
      ASSERT(AfxGetApp()->m_pszRegistryKey!=NULL && AfxGetApp()->m_pszRegistryKey[0]!=0);
      m_nCurrentLanguage=(LANGID)AfxGetApp()->GetProfileInt(_T(""),szLanguageId,0);
      if (LoadLanguage())
      return;
      m_nCurrentLanguage= GetUserUILanguage();
      if (LoadLanguage())
      return;
      m_nCurrentLanguage= GetSystemUILanguage();
      if (LoadLanguage())
      return;
      m_nCurrentLanguage= GetUserDefaultLangID();
      if (LoadLanguage())
      return;
      m_nCurrentLanguage= GetSystemDefaultLangID();
      if (LoadLanguage())
      return;
      m_nCurrentLanguage= m_nExeLanguage;
      VERIFY(LoadLanguage());
      }
      bool CLanguageSupport::LoadLanguage()
      {
      if (m_nCurrentLanguage==0)
      return false;
      if (LoadLanguageDll())
      return true;
      WORD wSubLanguage= SUBLANGID(m_nCurrentLanguage);
      if (wSubLanguage!=SUBLANG_NEUTRAL)
      {
      m_nCurrentLanguage= MAKELANGID( PRIMARYLANGID(m_nCurrentLanguage), SUBLANG_NEUTRAL );
      if (LoadLanguageDll())
      return true;
      }
      if (wSubLanguage!=SUBLANG_DEFAULT)
      {
      m_nCurrentLanguage= MAKELANGID( PRIMARYLANGID(m_nCurrentLanguage), SUBLANG_DEFAULT );
      if (LoadLanguageDll())
      return true;
      }
      return false;
      }
      typedef LANGID (WINAPI*PFNGETUSERDEFAULTUILANGUAGE)();
      typedef LANGID (WINAPI*PFNGETSYSTEMDEFAULTUILANGUAGE)();
      #if _MFC_VER<0x0700 // for VC6 users. Depending on your version of the platform SDK, you may need these lines or not.
      typedef LPARAM LONG_PTR;
      #endif
      static BOOL CALLBACK _EnumResLangProc(HMODULE /*hModule*/, LPCTSTR /*pszType*/,
      LPCTSTR /*pszName*/, WORD langid, LONG_PTR lParam)
      { // Helper used to identify the language in NTDLL.DLL (used for NT4)
      if(lParam == NULL) // lParam = ptr to var that receives the LangId
      return FALSE;
      LANGID* plangid = reinterpret_cast< LANGID* >( lParam );
      *plangid = langid;
      return TRUE;
      }
      LANGID CLanguageSupport::GetUserUILanguage()
      {
      HINSTANCE hKernel32= ::GetModuleHandle(_T("kernel32.dll"));
      ASSERT(hKernel32 != NULL);
      PFNGETUSERDEFAULTUILANGUAGE pfnGetUserDefaultUILanguage =
      (PFNGETUSERDEFAULTUILANGUAGE)::GetProcAddress(hKernel32, "GetUserDefaultUILanguage"); // NB: GetProcAddress() takes an ANSI string
      if(pfnGetUserDefaultUILanguage != NULL)
      {
      return pfnGetUserDefaultUILanguage();
      }
      else
      {
      return 0;
      }
      }
      LANGID CLanguageSupport::GetSystemUILanguage()
      {
      HINSTANCE hKernel32= ::GetModuleHandle(_T("kernel32.dll"));
      ASSERT(hKernel32 != NULL);
      PFNGETSYSTEMDEFAULTUILANGUAGE pfnGetSystemDefaultUILanguage =
      (PFNGETSYSTEMDEFAULTUILANGUAGE)::GetProcAddress(hKernel32, "GetSystemDefaultUILanguage"); // NB: GetProcAddress() takes an ANSI string
      if(pfnGetSystemDefaultUILanguage != NULL)
      {
      return pfnGetSystemDefaultUILanguage();
      }
      else
      {
      if (::GetVersion()&0x80000000)
      {
      DWORD dwLangID= 0; // Assume error
      HKEY hKey = NULL;
      LONG nResult = ::RegOpenKeyEx(HKEY_CURRENT_USER,
      _T( "Control Panel\\Desktop\\ResourceLocale" ), 0, KEY_READ, &hKey);
      if (nResult == ERROR_SUCCESS)
      {
      DWORD dwType;
      TCHAR szValue[16];
      ULONG nBytes = sizeof( szValue );
      nResult = ::RegQueryValueEx(hKey, NULL, NULL, &dwType, LPBYTE( szValue ), &nBytes );
      if (nResult==ERROR_SUCCESS && dwType==REG_SZ)
      _stscanf( szValue, _T( "%x" ), &dwLangID );
      ::RegCloseKey(hKey);
      }
      return (LANGID)dwLangID;
      }
      else
      {
      LANGID LangId = 0;
      HMODULE hNTDLL = ::GetModuleHandle( _T( "ntdll.dll" ) );
      if (hNTDLL != NULL)
      {
      ::EnumResourceLanguages( hNTDLL, RT_VERSION, MAKEINTRESOURCE( 1 ),
      _EnumResLangProc, reinterpret_cast< LONG_PTR >( &LangId ) );
      }
      return LangId;
      }
      }
      }
      bool CLanguageSupport::LoadLanguageDll()
      {
      if (m_nCurrentLanguage==m_nExeLanguage)
      {
      TRACE0("Resource DLL is... the EXE.\n");
      UnloadResourceDll();
      return true;
      }
      TCHAR szAbbrevName[4];
      if (GetLocaleInfo(MAKELCID(m_nCurrentLanguage, SORT_DEFAULT), LOCALE_SABBREVLANGNAME, szAbbrevName, 4)==0)
      {
      TRACE1("Attempt to load DLL for unsupported language. Language = %u\n", m_nCurrentLanguage);
      return false;
      }
      TCHAR szFilename[MAX_PATH];
      DWORD cch= GetModuleFileName( NULL, szFilename, MAX_PATH);
      ASSERT(cch!=0);
      LPTSTR pszExtension= PathFindExtension(szFilename);
      lstrcpy(pszExtension, szAbbrevName);
      lstrcat(pszExtension, _T(".dll"));
      HINSTANCE hDll = LoadLibrary(szFilename);
      if (hDll != NULL)
      {
      TRACE1("Resource DLL %s loaded successfully\n",szFilename);
      UnloadResourceDll();
      m_hDll= hDll;
      SetResourceHandle(m_hDll);
      return true;
      }
      else
      return false;
      }
      CLanguageSupport::CLanguageSupport()
      {
      m_hDll= NULL;
      m_nCurrentLanguage= 0;
      LookupExeLanguage();
      }
      CLanguageSupport::~CLanguageSupport()
      {
      UnloadResourceDll();
      }
      void CLanguageSupport::UnloadResourceDll()
      {
      if (m_hDll!=NULL)
      {
      SetResourceHandle(AfxGetApp()->m_hInstance); FreeLibrary(m_hDll);
      m_hDll= NULL;
      }
      }
      void CLanguageSupport::SetResourceHandle(HINSTANCE hDll)
      {
      AfxSetResourceHandle(hDll);
      #if _MFC_VER>=0x0700
      _AtlBaseModule.SetResourceInstance(hDll);
      #endif
      }
      LANGID CLanguageSupport::GetLangIdFromFile(LPCTSTR pszFilename)
      {
      DWORD dwHandle;
      DWORD dwLength=GetFileVersionInfoSize((LPTSTR)pszFilename,&dwHandle);
      if (dwLength==0)
      {
      TRACE(_T("Failed to read file’s version info. Error= %1!u!\n"), GetLastError());
      return 0;
      }
      LANGID nLangId=0;
      CByteArray abData;
      abData.SetSize(dwLength);
      if (GetFileVersionInfo((LPTSTR)pszFilename,dwHandle,dwLength,(LPVOID)abData.GetData()))
      {
      LANGID *pLanguageId; // NB: LANGID = WORD
      if (VerQueryValue(abData.GetData(),_T("\\VarFileInfo\\Translation"),(void**)&pLanguageId,(PUINT)&dwLength))
      nLangId=*pLanguageId;
      }
      return nLangId;
      }
      void CLanguageSupport::OnSwitchLanguage(UINT nId, bool bLoadNewLanguageImmediately)
      {
      int nLanguageIndex= nId-ID_LANGUAGE_FIRST;
      if (nLanguageIndex<0 || nLanguageIndex>=m_aLanguages.GetSize())
      return;
      LANGID LangId= m_aLanguages[nLanguageIndex];
      AfxGetApp()->WriteProfileInt(_T(""),szLanguageId,(int)LangId);
      if (bLoadNewLanguageImmediately)
      {
      LoadBestLanguage();
      }
      else
      {
      LANGID nCurrentLanguage= m_nCurrentLanguage;
      m_nCurrentLanguage= LangId;
      VERIFY(LoadLanguage());
      CString csFormat(MAKEINTRESOURCE(IDS_RESTART)); // Don’t forget to add a string in the String Table :
      m_nCurrentLanguage= nCurrentLanguage;
      VERIFY(LoadLanguage());
      CString csMessage;
      csMessage.FormatMessage(csFormat, LPCTSTR(AfxGetAppName())); // IDS_RESTART : Please restart %1.
      AfxMessageBox(csMessage,MB_ICONINFORMATION); // Please restart MyApp.
      }
      }
      void CLanguageSupport::GetAvailableLanguages()
      {
      m_aLanguages.SetSize(0);
      if (m_nExeLanguage!=0)
      m_aLanguages.Add(m_nExeLanguage);
      TCHAR szFileMask[MAX_PATH+10];
      DWORD cch= GetModuleFileName( NULL, szFileMask, MAX_PATH);
      ASSERT(cch!=0);
      LPTSTR pszExtension= PathFindExtension(szFileMask);
      lstrcpy(pszExtension, _T("???.dll"));
      CFileFind finder;
      BOOL bWorking = finder.FindFile(szFileMask);
      while (bWorking)
      {
      bWorking = finder.FindNextFile();
      LANGID nLanguageID=GetLangIdFromFile(finder.GetFilePath());
      if (nLanguageID!=0)
      m_aLanguages.Add(nLanguageID);
      }
      }
      void CLanguageSupport::CreateMenu(CCmdUI *pCmdUI, UINT nFirstItemId)
      {
      GetAvailableLanguages();
      UINT nCurrentItem= 0;
      CMenu SubMenu;
      SubMenu.CreatePopupMenu();
      for (int i=0; i 
      {
      SubMenu.AppendMenu(MF_STRING, ID_LANGUAGE_FIRST+i, GetLanguageName(m_aLanguages[i]) );
      if (m_nCurrentLanguage==m_aLanguages[i])
      nCurrentItem= ID_LANGUAGE_FIRST+i;
      }
      if (nCurrentItem!=0)
      SubMenu.CheckMenuRadioItem(ID_LANGUAGE_FIRST, ID_LANGUAGE_FIRST+(int)m_aLanguages.GetSize()-1, nCurrentItem, MF_BYCOMMAND);
      MENUITEMINFO mii= { sizeof(mii) };
      mii.fMask= MIIM_SUBMENU;
      mii.hSubMenu= SubMenu.m_hMenu;
      ::SetMenuItemInfo(pCmdUI->m_pMenu->m_hMenu, pCmdUI->m_nID, FALSE, &mii);
      pCmdUI->Enable();
      SubMenu.Detach();
      }
      CString CLanguageSupport::GetLanguageName(LANGID wLangId)
      {
      TCHAR szLanguage[200], szCP[10];
      GetLocaleInfo( MAKELCID(wLangId, SORT_DEFAULT), LOCALE_IDEFAULTANSICODEPAGE, szCP, 10);
      int nAnsiCodePage= _ttoi(szCP);
      int cch= GetLocaleInfo( MAKELCID(wLangId, SORT_DEFAULT), LOCALE_SNATIVELANGNAME, szLanguage, countof(szLanguage));
      if (cch!=0)
      {
      #ifndef UNICODE
      wchar_t szLanguageW[200];
      cch= MultiByteToWideChar(nAnsiCodePage, MB_ERR_INVALID_CHARS, szLanguage, -1, szLanguageW, countof(szLanguageW));
      BOOL bUsed= FALSE;
      cch= WideCharToMultiByte(CP_ACP, 0, szLanguageW, -1, szLanguage, countof(szLanguage), "X", &bUsed);
      if (bUsed || nAnsiCodePage==0)
      {
      cch= 0;
      }
      #endif
      }
      if (cch==0)
      {
      cch= GetLocaleInfo( MAKELCID(wLangId, SORT_DEFAULT), LOCALE_SLANGUAGE, szLanguage, countof(szLanguage));
      if (cch==0)
      _stprintf(szLanguage, _T("%u - ???"), wLangId); // Ouch ! We can’t even display the name in the current language !
      }
      return szLanguage;
      }
      void CLanguageSupport::LookupExeLanguage()
      {
      TCHAR szFilename[MAX_PATH]={0};
      DWORD cch= GetModuleFileName( NULL, szFilename, MAX_PATH);
      ASSERT(cch!=0);
      m_nExeLanguage= GetLangIdFromFile(szFilename);
      }