字符串表资源(STRINGTABLE)和程序本地化
在多语言编程中,使用UpdateResource函数可实现资源本地化,如对话框、菜单、版本等资源的本地化。然字符串表的本地化需要进行特别的处理,这就需要对字符串表资源的二进制数据结构有所了解。
1. 字符串表资源的二进制数据结构
字符串表资源使用STRINGTABLE语句定义,但串表设有名称或ID。
FindResourceEx函数对字符串资源的内存格式描述为: 字符串资源存储在每个组最多16个字符串的组中。每个组中字符串都以Unicode格式存储为字符串长度和字符串值(不一定以NULL结尾)。LoadString函数将从其对应的部分提取字符串资源。
根据对字符串资源的内存格式分析,为了提高字符串的检索效率,字符串资源以分组方式存贮,如果用FindResourceEx函数来搜索字符串,则只能搜索字符串组(这与搜索RT_GROUP_CURSOR和RT_GROUP_ICON类资源有点类似),然后再使用列遍的方法在搜索到的组内搜索对应ID的字符串。
字符串组的二进制数据结构如下:
第1组:
dw ? ;ID=00h的字符串的长度(WORD单位,以下同)
dw .... ;字符串值(Unicode格式)。如果字符串的长度=0,则无该内容(以下同理)。
dw ? ;ID=01h的字符串的长度
dw .... ;字符串值。
......
dw ? ;ID=0fh的字符串的长度
dw .... ;字符串值
如果资源中未定义ID=0-0fh的字符串,则没有该组的内容(以下同理)。
第2组:
dw ? ;ID=10h的字符串的长度
dw .... ;字符串值
dw ? ;ID=11h的字符串的长度
dw .... ;字符串值
......
dw ? ;ID=1fh的字符串的长度
dw .... ;字符串值
第3组:
......
例: 如果RC资源文件中定义以下的字符串表:
STRINGTABLE
BEGIN
0 "A000000000"
1 "B000000001"
3 "C000000003"
4 "D000000004"
35 "E000000035"
38 "F000000038"
END
则字符串资源内存格式如下:
第1组:
dw 0ah ;ID=00h的字符串的长度
dw "A000000000" ;串值(这不是合法的语句,只表示它是一个Unicode串,下同)
dw 0ah ;ID=01h的字符串的长度
dw "B000000001"
dw 00 ;ID=02h的字符串的长度
dw 0ah ;ID=03h的字符串的长度
dw "C000000003"
dw 0ah ;ID=04h的字符串的长度
dw "D000000004"
dw 00h ;ID=05h的字符串的长度
dw 00h ;ID=06h的字符串的长度
dw 00h ;ID=07h的字符串的长度
dw 00h ;ID=08h的字符串的长度
dw 00h ;ID=09h的字符串的长度
dw 00h ;ID=0ah的字符串的长度
dw 00h ;ID=0bh的字符串的长度
dw 00h ;ID=0ch的字符串的长度
dw 00h ;ID=0dh的字符串的长度
dw 00h ;ID=0e0h的字符串的长度
dw 00h ;ID=0fh的字符串的长度
第2组: 无该组内容。因为未定义10h-1fh的字符串。
第3组:
dw 00h ;ID=20h的字符串的长度
dw 00h ;ID=21h的字符串的长度
dw 00h ;ID=22h的字符串的长度
dw 0ah ;ID=23h(35)的字符串的长度
dw "E000000035"
dw 00h ;ID=24h的字符串的长度
dw 00h ;ID=25h的字符串的长度
dw 0ah ;ID=26h(38)的字符串的长度
dw "F000000038"
dw 00h ;ID=27h的字符串的长度
dw 00h ;ID=28h的字符串的长度
dw 00h ;ID=29h的字符串的长度
dw 00h ;ID=2ah的字符串的长度
dw 00h ;ID=2bh的字符串的长度
dw 00h ;ID=2ch的字符串的长度
dw 00h ;ID=2dh的字符串的长度
dw 00h ;ID=2eh的字符串的长度
dw 00h ;ID=2fh的字符串的长度
2. 字符串表本地化例子
2.1 多语言字符串表资源
写一个多语言字符串表资源文件,文件名假定的mui.rc,该资源文件包含以下3种语言的字符串表资源。并使用该资源创建一个简单的DLL,假定该DLL文件名为mui.dll。该DLL没有导出函数,只包含多语言资源,供本地化使用。
#define IDS_FILE 1000
#define IDS_EXIT 9001
#define IDS_UNDO 1002
#define IDS_CUT 1003
#define IDS_COPY 1004
#define IDS_PASTE 1005
#define IDS_FONT 1015
#define IDS_COLOR 1016
// 英文
LANGUAGE 9,1 //0x409
STRINGTABLE
BEGIN
IDS_FILE "File"
IDS_EXIT "Exit"
IDS_UNDO "Undo"
IDS_CUT "Cut"
IDS_COPY "Copy"
IDS_PASTE "Paste"
IDS_FONT "Font"
IDS_COLOR "Color"
END
// 简体中文
LANGUAGE 4,2 //0x804
STRINGTABLE
BEGIN
IDS_FILE "文 件"
IDS_EXIT "退 出"
IDS_UNDO "撤 消"
IDS_CUT "剪 切"
IDS_COPY "复 制"
IDS_PASTE "粘 贴"
IDS_FONT "字 体"
IDS_COLOR "颜 色"
END
// 繁体中文
LANGUAGE 4,1 //0x404
STRINGTABLE
BEGIN
IDS_FILE "文 件"
IDS_EXIT "退 出"
IDS_UNDO "撤 消"
IDS_CUT "剪 切"
IDS_COPY "復 製"
IDS_PASTE "粘 貼"
IDS_FONT "字 體"
IDS_COLOR "顏 色"
END
2.2 编写一个串表更新子程序
根据FindResourceEx函数的描述,字符串是以组为单位进行搜索和装载的,如果要对字符串表实现本地化,则也应以组为单位进行本地化。
以下的子程序用于对指定的字符串资源进行更新,以实现本地化。
;==========================================================
;更新指定的字符串表资源---用于字符串资源的本地化
;入: hUpdate=目标文件的更新句柄(BeginUpdateResource函数返回)
; wMinId=要更新的最小字符串资源ID
; wMaxId=要更新的最大字符串资源ID
; hInst=源资源模块句柄
; ulang=源模块资源中用于更新的资源语言
;出: NO
;注: 如果字符串资源ID的间隔值较大,则可分段更新
;=========================================================
Update_StringTable proc hUpdate:QWORD,wMinId:QWORD,\
wMaxId:QWORD,hInst:QWORD,\
ulang:DWORD
LOCAL ss_size:DWORD
LOCAL ss_hRes:QWORD
;---将字符串ID转换为组序号---
mov rax,wMinId
shr rax,4 ;/16
inc rax
mov wMinId,rax ;组号
mov rax,wMaxId
shr rax,4 ;/16
inc rax
mov wMaxId,rax ;组号
;---在源资源模块中搜索资源---
ss_lp1:
invoke FindResourceEx,hInst,RT_STRING,wMinId,ulang
test rax,rax
jz ss_nt
mov ss_hRes,rax
invoke SizeofResource,hInst,ss_hRes
mov ss_size,eax ;资源尺寸
invoke LoadResource,hInst, ss_hRes
test rax,rax
jz ss_nt
invoke LockResource,rax
test rax,rax
jz ss_nt
;---复制资源---
mov r10,rax
invoke UpdateResource,hUpdate,RT_STRING,wMinId,0,r10,ss_size
ss_nt:
inc wMinId
mov rax,wMinId
cmp rax,wMaxId
jbe ss_lp1 ;搜索下一个组
ret
Update_StringTable endp
为了本地化的完整性,另写一个子程序,以下子程序用于更新普通的资源。
;==========================================================
;更新指定的资源
;入: hUpdate=目标文件的更新句柄(BeginUpdateResource函数返回)
; rType=要更新的资源类型
; wId=要更新的资源ID
; hInst=源资源模块句柄
; uId=源模块资源中用于更新的资源ID
; ulang=源模块资源中用于更新的资源语言
;出: RAX=TRUE: 成功
; =FALSE: 失败
;=========================================================
Update_Rsrc proc hUpdate:QWORD,rType:QWORD,wId:QWORD,
hInst:QWORD,uId:QWORD,ulang:DWORD
LOCAL ss_size:DWORD
LOCAL ss_hRes:QWORD
;—在源资源模块中搜索资源—
invoke FindResourceEx,hInst,rType,uId,ulang
test rax,rax
jz ss_0
mov ss_hRes,rax
invoke SizeofResource,hInst,ss_hRes
mov ss_size,eax
invoke LoadResource,hInst, ss_hRes
test rax,rax
jz ss_0
invoke LockResource,rax
test rax,rax
jz ss_0
;—复制资源—
mov r10,rax
invoke UpdateResource,hUpdate,rType,wId,0,r10,ss_size
ss_0:
ret
Update_Rsrc endp
2.3 本地化操作
软件的本地化一般是在安装时完成的,假如你需要本地化的程序文件名为test.exe,则安装包中应包含用于本地化的mui.dll文件和安装程序install.exe。
安装程序install.exe中包含以下子程序,根据用户所选择的应用语言环境,更新test.exe程序中的资源。更新完成后,mui.dll文件和安装程序install.exe不再需要,可以删除。
;=======================================
;本地化示例子程序
;入: wlang=test.exe程序的应用环境语言:
; 409h--美国英语
; 804h--简体中文
; 404h--繁体中文
;=======================================
App_Local proc wlang:DWORD
LOCAL ss_hInst:QWORD
LOCAL ss_hUpdate:QWORD
;---装载源DLL文件,其中包含要复制的资源---
invoke LoadLibrary,"mui.dll"
mov ss_hInst,rax
test rax,rax
jz ss_0
;---打开目标文件的更新句柄---
invoke BeginUpdateResource,"test.exe", FALSE
test rax,rax
jz ss_out
mov ss_hUpdate,rax
;---更新字符串表资源(最小ID=IDS_FILE,最大ID=IDS_EXIT)---
invoke Update_StringTable,ss_hUpdate,IDS_FILE,IDS_EXIT,ss_hInst,wlang
;-------------------------------------------------
; 如果mui.dll文件中还包含消息表、菜单、版本等资源,
; 则调用Update_Rsrc函数进行更新
;-------------------------------------------------
;---更新版本资源---
invoke Update_Rsrc,ss_hUpdate,RT_VERSION,VS_VERSION_INFO,\
ss_hInst,VS_VERSION_INFO,wlang
;---更新菜单资源---
invoke Update_Rsrc,ss_hUpdate,RT_MENU,IDM_MAIN,\
ss_hInst,IDM_MAIN,wlang
;---更新消息表资源---
invoke Update_Rsrc,ss_hUpdate,RT_MESSAGETABLE,1,ss_hInst,1,wlang
;---完成更新---
invoke EndUpdateResource,ss_hUpdate,FALSE
ss_out:
invoke FreeLibrary,ss_hInst
ss_0:
ret
App_Local endp
2.4 其他说明
如果你的Windows系统中有LoadMUILibrary函数,则可以使用.mui文件来包含多语言资源,而不使用DLL文件。用rc.exe编译mui.rc资源文件,来获取.mui文件,用LoadMUILibrary装载.mui文件,并用FreeMUILibrary释放。其他本地化操作与上述类似。
;—完成更新—
invoke EndUpdateResource,ss_hUpdate,FALSE
ss_out:
invoke FreeLibrary,ss_hInst
ss_0:
ret
App_Local endp
## 2.4 其他说明
如果你的Windows系统中有LoadMUILibrary函数,则可以使用.mui文件来包含多语言资源,而不使用DLL文件。用rc.exe编译mui.rc资源文件,来获取.mui文件,用LoadMUILibrary装载.mui文件,并用FreeMUILibrary释放。其他本地化操作与上述类似。