资源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); } |
|
|