这几天一直在研究这个东东,总算小有所成,中间走了不少弯路,摔了不少跟头,也学到了不少知识。
之所以把这点东西写出来,是为了让和我曾经一样迷茫的兄弟们找到一点方向,不要像我一样浪费那么多时间在一些无谓的事情上。
实现软件国际化目前有两种方式:
第一种是像FLASHGET那样将菜单文本等按资源ID写入一个TXT文件,需要时提取文本,刷新窗体,这种方式的弊端就是语言选择菜单内的语言种类是固定的,想要添加/删除一种语言要修改主程序,而且不方便管理,甚至会造成资源无法完全本地的问题。
第二种是用资源DLL实现软件的国际化,是MS推荐的方式,也是比较好管理的方式,这种方式可以动态的添加和删除附属语言DLL而不用更改主程序的代码,效率高,管理方便。
下面我就说说我在windows moblie下是如何实现第2种方法的。
1。建立一个win32应用程序MUL_LAN 作为主程序,并且添加rc档作为默认的菜单
//
// Menu
//
IDR_MENU MENU
BEGIN
POPUP "menu"
BEGIN
MENUITEM "文件", ID_MENU_FILE
MENUITEM "语言", IDM_LANG//选择语言的菜单按键
MENUITEM "关于", IDM_ABOUT
END
END
// Dialog
//
IDD_ABOUTBOX DIALOG 0, 0, 156, 129
STYLE DS_SETFONT | DS_FIXEDSYS | WS_POPUP | WS_CAPTION
EXSTYLE 0x80000000L
CAPTION "关于 MUL_LAN"
FONT 9, "MS Shell Dlg"
BEGIN
ICON IDI_MUL_LAN,IDC_STATIC_1,12,12,20,20,SS_REALSIZEIMAGE
LTEXT "MUL_LAN 1.0 版",IDC_STATIC_2,12,36,70,8,SS_NOPREFIX
LTEXT "版权所有 (C) 2006",IDC_STATIC_3,12,48,66,8
END
IDD_ABOUTBOX_WIDE DIALOG 0, 0, 210, 129
STYLE DS_SETFONT | WS_POPUP | WS_CAPTION
EXSTYLE 0x80000000L
CAPTION "关于 MUL_LAN"
FONT 9, "MS Shell Dlg"
BEGIN
ICON IDI_MUL_LAN,IDC_STATIC_1,12,12,21,20,SS_REALSIZEIMAGE
LTEXT "MUL_LAN 1.0 版",IDC_STATIC_2,48,12,66,8,SS_NOPREFIX
LTEXT "版权所有 (C) 2006",IDC_STATIC_3,48,24,66,8
END
IDD_COMBOBOX DIALOG 0, 0, 186, 95
STYLE DS_SETFONT | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "Dialog"
FONT 8, "MS Shell Dlg"
BEGIN
DEFPUSHBUTTON "确定",IDOK,129,7,50,14
PUSHBUTTON "取消",IDCANCEL,129,24,50,14
LTEXT "请选择语言",IDC_STATIC,7,7,41,8
COMBOBOX IDC_COMBO1,7,22,111,131,CBS_DROPDOWN | CBS_SORT | WS_VSCROLL |
END
//选择语言的DialogBox,上面有一个combobox用来动态列出可以使用的附属语言。
2。建立对应的附属语言DLL项目,添加资源档,其他可以不动,打开属性面板在链接器-〉高级-〉将“无入口点”属性改为“/NOENTRY”,以表明此项目的输出是纯资源DLL。
3。在主程序的MUL_LAN.cpp当中的主窗体消息处理函数
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)当中添加对“IDM_LANG”的处理
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
……
case IDM_LANG:
DialogBox(g_hInst,(LPCTSTR)IDD_COMBOBOX,hWnd,(DLGPROC)Language);
break;
……
}
3。加载被选择语言对应DLL的函数
LRESULT CALLBACK Language(HWND hDlg,UINT message,WPARAM wParam,LPARAM)
{
int index;
LRESULT NewUILanguage;
HWND hwndCombo = GetDlgItem(hDlg,IDC_COMBO1);
HWND hMainWindow = GetParent(hDlg);
HMENU hNewMenu;
HMENU g_hMenu;
DWORD error;
switch (message)
{
case WM_INITDIALOG:
PopulateLanguages(hwndCombo);//寻找DLL目录并填充语言选择下拉cai
return TRUE;
case WM_COMMAND:
switch (LOWORD(wParam)) {
case IDOK:
index = SendMessage(hwndCombo, CB_GETCURSEL, 0, 0);
if( index != CB_ERR ) {
NewUILanguage = SendMessage(hwndCombo, CB_GETITEMDATA, index, 0);
if( NewUILanguage != CB_ERR && NewUILanguage != UILanguage ) {
UILanguage = (LANGID)NewUILanguage;
hSatDLL=LoadSatelliteDLL(UILanguage);
if( hSatDLL == NULL ) {
hSatDLL = g_hInst;
}
SetLastError(0);
hNewMenu=LoadMenu(hSatDLL,MAKEINTRESOURCE(IDR_MENU));
error=GetLastError();
SHMENUBARINFO mbi;//创建新的菜单栏
memset(&mbi, 0, sizeof(SHMENUBARINFO));
mbi.cbSize = sizeof(SHMENUBARINFO);
mbi.hwndParent = hMainWindow;
mbi.nToolBarId =IDR_MENU ;
mbi.hInstRes =hSatDLL;
if (!SHCreateMenuBar(&mbi))
{
error=GetLastError();
g_hWndMenuBar = NULL;
}
else
{
g_hWndMenuBar = mbi.hwndMB;
}
}
}
case IDCANCEL:
EndDialog(hDlg, LOWORD(wParam));
return TRUE;
}
break;
}
return FALSE;
}
// 填充选择对话框中的语言列表
// 根据用十进制 LCID 命名的子目录
int PopulateLanguages(HWND hwndCombo) {
TCHAR CurrentDirectory[MAX_PATH];
TCHAR CurrentFile[MAX_PATH];
int AvailableLangID;
TCHAR AvailableLangName[MAX_LANGNAME];
wchar_t *prt;
int PathLength;
HANDLE hDir;
int i = 0;
int j;
// 检索当前目录名
GetModuleFileName(NULL,CurrentFile,sizeof(CurrentFile)/sizeof(TCHAR));//当前文件
prt=wcsrchr(CurrentFile,TEXT('\\'));
PathLength=prt-CurrentFile;
wcsncpy_s(CurrentDirectory,sizeof(CurrentDirectory)/sizeof(TCHAR),CurrentFile,PathLength);
wcscat_s(CurrentDirectory,sizeof(CurrentDirectory)/sizeof(TCHAR),_T("\\*.*"));
// 循环访问所有目录并填充语言选择的
// 下拉列表
hDir = FindFirstFile(CurrentDirectory, &FindFileData);
if( hDir == INVALID_HANDLE_VALUE ) {
FindClose(hDir);
SendMessage(hwndCombo,CB_ADDSTRING,0,(LPARAM)CurrentDirectory);
return 0;
}
SendMessage(hwndCombo, CB_RESETCONTENT, 0, 0);
do {
if( FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) {
AvailableLangID = _ttoi(FindFileData.cFileName);
if( AvailableLangID ) {
if( GetLocaleInfo(AvailableLangID,LOCALE_SNATIVELANGNAME,AvailableLangName,MAX_LANGNAME)) {
CurrentIndex = SendMessage(hwndCombo, CB_INSERTSTRING , 0, (LPARAM)AvailableLangName);
SendMessage(hwndCombo, CB_SETITEMDATA, (WPARAM)CurrentIndex, (LPARAM)AvailableLangID);
i++;
}
}
}
}while ( FindNextFile(hDir, &FindFileData) );
FindClose(hDir);
// 为了设置当前选择,我们必须循环访问组合框
// 因为添加操作按字母顺序排序组合框
for(j=0 ; j<=i ; j++) {
if( UILanguage == SendMessage(hwndCombo, CB_GETITEMDATA, j, 0) )
SendMessage(hwndCombo, CB_SETCURSEL, (WPARAM)j, 0);
}
return i;
}
//加载选定语言DLL
HMODULE LoadSatelliteDLL(LANGID DesiredLanguage) {
TCHAR CurrentFile[MAX_PATH];
TCHAR CurrentDirectory[MAX_PATH];
TCHAR SatellitePath[MAX_PATH];
TCHAR buffer[100];
HMODULE hDLL;
int PathLength;
wchar_t* prt;
DWORD error;
// WIN32_FIND_DATA FindFileData;
//wchar_t* DLLname;
GetModuleFileName(NULL,CurrentFile,sizeof(CurrentFile)/sizeof(TCHAR));//当前文件
prt=wcsrchr(CurrentFile,TEXT('\\'));
PathLength=prt-CurrentFile+1;
wcsncpy_s(CurrentDirectory,sizeof(CurrentDirectory)/sizeof(TCHAR),CurrentFile,PathLength);
// // 先尝试加载具有完全指定语言的库
wcscpy_s(SatellitePath,sizeof(SatellitePath)/sizeof(TCHAR),CurrentDirectory);
_itot_s(DesiredLanguage,buffer, sizeof(buffer)/sizeof(TCHAR), 10);
wcscat_s(SatellitePath,sizeof(SatellitePath)/sizeof(TCHAR),buffer);
wcscat_s(SatellitePath,sizeof(SatellitePath)/sizeof(TCHAR),_T("\LANG.dll"));
hDLL=LoadLibrary( SatellitePath);
if( hDLL )
return hDLL;
else { // 尝试主语言 ID
_tcscpy_s(SatellitePath, sizeof(SatellitePath)/sizeof(TCHAR), CurrentDirectory);
DesiredLanguage = PRIMARYLANGID(DesiredLanguage);
_itot_s(DesiredLanguage,buffer,sizeof(buffer)/sizeof(TCHAR),10);
wcscat_s(SatellitePath,sizeof(SatellitePath)/sizeof(TCHAR),buffer);
wcscat_s(SatellitePath,sizeof(SatellitePath)/sizeof(TCHAR),_T("\1233.dll"));
hDLL = LoadLibraryEx(SatellitePath,NULL,DONT_RESOLVE_DLL_REFERENCES);
if( hDLL )
return hDLL;
else
return NULL;
}
}
就是这么简单,这个程序参考了MSDN的例程,但是关键部分都是自己重写的。
其中遇到的各种问题我也总结一下:
1.WINCE当中没有currentdirectory这个概念,如果想要加载DLL必须要使用绝对路径,问题是怎么找到DLL的绝对路径呢?方法是调用GetModuleFileName()函数,原形可以查MSND得到,该函数的第一个参数如果为NULL,则返回最后一个发出WM_CREAT 消息的文件的绝对路径,根据这个路径就可以找到DLL的路径。
2. WINCE当中使用的是UNICODE,对应的操作其实不难,只要记住:将char声明改称TCHAR,将CHAR* 改成wchar_t*,字符串引用都用TEXT宏包起来,搞定!
对应的还有几个字符串操作的函数,UNICODE的版本都是WCS开头的,比如wcscat_s(是wcscat的安全版本)是unicode版的字符串追加宏,强烈建议使用加_s而不是没有的的版本。原因不多说了,安全嘛呵呵,尤其是在拼路径的时候。
3.这个问题是浪费我时间的第2大罪魁祸首,他就是GetLastError();
此函数相当无聊,在我调用LoadLibrary的时候他不停的抱错,比如说代码6号,invalid module handle,再比如说126号 module handle could not found, 无知的我努力的寻找错误的根源,最终发现,众里寻她千百度。。。
强烈建议各位朋友在LoadLibrary 有返回值得时候别去管GetlastError给出的错误,完全是SHIT~
顺便提一下,我的LoadLibrary返回值unused是负数,如果你也返回了类似的数不要慌,转换成16进制看看~
4.第一大罪魁 模拟器 他加载了主程序的模块,但是不加载DLL模块。所以生成新菜单总是失败,害得我几乎把程序重写一遍都找不到错误。奉劝和我一样的新手们,一定要设备调试。。。。
就是这样啦,这是我完成的WINCE上的第一个任务,一路跌跌撞撞,总算是完成了,写出来和菜鸟们共同学习,希望你们不要跟我一样为了这些回头想想非常搞笑的问题浪费时间甚至暴跳如雷。
老鸟们可以扔鸡蛋了,谢谢~~