資源DLL與語言選擇菜單的實現
資源DLL與語言選擇菜單的實現
簡介
在當今這個發展越來越快的世界中,軟件的本地化及翻譯工作越來越重要,極大地關係到軟件的銷量及普及率;就拿常見的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 <afxcoll.h>
#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 <shlwapi.h>
#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<m_aLanguages.GetSize(); 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);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.