使用FormatMessage函数实现多语言消息

使用FormatMessage函数实现多语言消息

我们编写的程序常使用消息框来显示各类提示消息,如错误信息、确认信息等。在多语言编程中要求使用与地区语言相对应的字符文本。实现这种多语言的方法可能很多,本文介绍如何使用FormatMessage函数和消息文本,来实现多语言消息显示。

1. 消息文本文件格式

本节内容考参网站资料:
https://learn.microsoft.com/zh-cn/windows/win32/eventlog/message-text-files

使用多语言消息显示,需要使用"消息文本文件"。有关消息文本文件的详细说明请参考Windows API 文档。

消息文本文件用于定义多语言的消息。mc.exe编译器对该类文件进行编译,生成资源编译器所需要的消息表资源信息(即MESSAGETABLE资源),这样就可以使用FormatMessage函数实现多语言消息的显示。

消息文本文件中语句的一般语法如下所示:

关键字=值

忽略等号两边的空格,值之间和值与下一个关键词之间用空格分隔(包括换行符)。关键字忽略大小写。值使用 C/C++ 语法中整数常量、以及遵循 C/C++ 标识符规则的符号名称,并可使用不大于8个字符的文件名(不带扩展名)。

1.1 注释

消息文本文件中允许有注释行。以分号(;)开头的注释到行尾结束。分号后面跟着一个C/ c++单行注释指示符(//),这样消息编译器生成的头文件中的注释就符合 C/ c++单行注释的要求。

例:

;// This is a single-line comment.

对于块注释,每行均以分号开始,在第一行的分号后面放置一个 C/C++ 打开块注释指示器 (/) ,最后一行的分号后放置关闭块注释指示器 (/) 。

例:

;/* This is a block comment.
;   It spans multiple lines.
;*/

1.2 标题部分

消息文本文件包含一个标头,用于定义供文件正文中的消息定义使用的名称和语言标识符。 标头包含以下语句的零个或多个。

(1)MessageIdTypedef=type: 在消息定义中使用的类型,如下所示:

#define name ((type)0xnnnnnnnn)

类型必须足够大,才能容纳整个消息代码,例如 DWORD。 该类型也可以是应用程序源代码中定义的类型。 类型的默认值为空,因此不使用类型强制转换。

可以在标头中指定此语句,并在消息定义节中尽可能多地指定此语句。

例:
MessageIdTypedef=DWORD

(2)SeverityNames=(name=number[:name]) : 设置消息严重性名称。严重性名称关联的是一个数字,当向左移动30位时,使用facility和消息ID值的逻辑OR提供位模式以形成消息代码。任何不适合2位的严重性值都是错误。 还可以为严重性代码提供符号别名。

即消息严重性名称的定义数值为0-3(即2位),表示消息的级别,其与facility和消息ID值的逻辑OR形成一个消息代码。

默认值定义如下:SeverityNames= ( Success=0x0 Informational=0x1 Warning=0x2 Error=0x3)

如果指定别名,则如下:
SeverityNames=(Success=0x0:STATUS_SEVERITY_SUCCESS
Informational=0x1:STATUS_SEVERITY_INFORMATIONAL
Warning=0x2:STATUS_SEVERITY_WARNING
Error=0x3:STATUS_SEVERITY_ERROR)

(3)FacilityNames=(name=number[:name]) : 设置消息的场所名称。与每个名称关联的是一个数字,当向左移动16位时,使用severity和消息ID值的逻辑OR提供位模式以形成消息代码。任何不适合12位的设置值都是错误。 这允许4096个代码;前256个代码保留供系统使用。还可以为代码提供符号名称。

即该值表示消息发生的场所,如系统错误,应用程错误等。

默认值定义如下:FacilityNames= ( System=0x0FF Application=0xFFF)

(4)LanguageNames=(name=number:filename): 消息定义中允许的语言名称。 该数字用作资源表中的语言标识符。指定的文件包含该语言的消息。 它通常是消息编译器生成的.bin 文件。

示例值为:LanguageNames=(English=0x409:MSG00409)

(5)OutputBase=number: 消息编译器写入头文件的消息常量输出基数。如果存在,此值将替代 -d 开关。 此数字可以是 10 (十进制) 或 16 (十六进制) 。

1.3 消息定义

消息文本文件包含标头节后面的零个或多个消息定义。下面描述了消息定义语句。MessageId语句标记消息定义的开头。Severity和Facility语句是可选的。

(1)MessageId=[number|+number] : 消息的标识符。此语句是必需的。如果未指定任何值,则使用的值为前一个facility值,加上1。 如果使用加号指定值,则使用的值为前一个facility值,加上加号后面的数字。指定的任何值必须适合16位。

(2)Severity=name: 标头中 SeverityNames 指定的值之一。 此语句是可选的。 如果未指定任何值,则使用的值是最后为消息定义指定的值。 第一个消息定义的默认值为 Severity=Success

(3)Facility=name: 标头中的 FacilityNames 指定的值之一。 此语句是可选的。 如果未指定任何值,则使用的值是最后为消息定义指定的值。 第一个消息定义的默认值为 Facility=Application

(4)SymbolicName=name: 将 C/C++ 符号常量与消息代码相关联。 它在消息定义中使用,如下所示:

#define名称 ( (类型) 0xnnnnnn)

该消息别名是实际消息代码,它组合了Severity、Facility和MessageId(见例子)。

(5)OutputBase={number}: 消息编译器写入头文件的消息常量输出基数。如果存在,此值将替代 -d 开关。 此数字可以是 10 (十进制) 或 16 (十六进制) 。

(6)Language=name: 标头中 LanguageNames 指定的值之一。 此语句是可选的。 如果未指定任何值,则使用的值是最后为消息定义指定的值。 第一个消息定义的默认值如下所示: Language=English

(7)消息文本: 消息的文本。它包含在消息二进制文件中。

(8). : 消息文本由包含行开头的单个句点符(“.”)的新行终止。

如果消息定义包含多个语言的消息文本,则每种语言都需要其自己的 Language 语句、消息文本以及用句点符号(“.”)终止新行。 例如:

  MessageId=0x1
  Severity=Error
  Facility=Runtime
  SymbolicName=MSG_BAD_COMMAND
  Language=English
  You have chosen an incorrect command.
.
  Language=Japanese
  <Japanese message string goes here>
.

1.4 脱字符

可以在消息文本指定以下转义序列。百分比符号字符 (%) 开始所有转义序列。没有百分比符号后的任何其他字符都显示。

(1)%n[!format_specifier!] : 格式化插入项。每个插入都是FormatMessage函数的Arguments数组中的条目。 n值可以是介于1和99之间的数字。格式说明符是可选的。如果未指定任何值,则默认值为!s!。有关格式说明符的信息,请参阅wsprintf函数。

格式说明符可以使用*表示精度或宽度。当指定时,它们使用编号为n+1和n+2的插入。

(2)%0 : 终止消息文本行,而无需尾随换行符。这可用于生成长行或终止提示消息,而无需尾随换行符。

(3)%. : 生成单个句点。这可用于在行的开头显示句点,否则将终止消息文本。

(4)%! : 生成单个感叹号。 这可用于在插入后立即指定感叹号。

(5)%% : 生成单一百分比符号。

(6)%n : 在行末尾发生硬换行符。 这可与 FormatMessage 一起使用,以确保消息符合特定宽度。

(7)%b : 生成空格字符。 这可用于确保行上的相应尾随空格数。

(8)%r : 在不带尾随换行符的情况下生成硬回车符。

2. 编辑一个消息文本文件

此消息文本文件名为Messages.mc,包含注释块,后跟标题部分,后跟英文、简体中文和繁体中文三种语言的消息。必须使用与Unicode 兼容的编辑器同时支持不同书面语言中使用的字符。

; // 消息文本文件
; // ---标题部分---
 MessageIdTypedef=DWORD

SeverityNames=(Success=0x0:STATUS_SEVERITY_SUCCESS
Informational=0x1:STATUS_SEVERITY_INFORMATIONAL
Warning=0x2:STATUS_SEVERITY_WARNING
Error=0x3:STATUS_SEVERITY_ERROR)

FacilityNames=(System=0x0:FACILITY_SYSTEM
Runtime=0x2:FACILITY_RUNTIME
Stubs=0x3:FACILITY_STUBS
Io=0x4:FACILITY_IO_ERROR_CODE)

; // 定义语言名称(英文、简体中文和繁体中文),和对应的.bin文件名
LanguageNames=(English=0x409:MSG00409)
LanguageNames=(ChineseTraditional=0x404:MSG00404)
LanguageNames=(ChineseSimplified=0x804:MSG00804)

; // 以下是消息定义。

MessageId=0x1
Severity=Error
Facility=Runtime
SymbolicName=MSG_BAD_COMMAND
Language=English
You have chosen an incorrect command.
.
Language=ChineseSimplified
您选择了一个错误的命令。
.
Language=ChineseTraditional
您選擇了一個錯誤的命令。
.

MessageId=0x2
Severity=Warning
Facility=Io
SymbolicName=MSG_BAD_PARM1
Language=English
Cannot reconnect to the server.
.
Language=ChineseSimplified
无法重新连接到服务器。
.
Language=ChineseTraditional
無法重新連接到服務器。
.

MessageId=0x3
Severity=Success
Facility=System
SymbolicName=MSG_STRIKE_ANY_KEY
Language=English
Press any key to continue . . . %0
.
Language=ChineseSimplified
按任意键继续 . . . %0
.
Language=ChineseTraditional
按任意鍵繼續 . . . %0
.

MessageId=0x4
Severity=Error
Facility=System
SymbolicName=MSG_CMD_DELETE
Language=English
File %1 contains %2 which is in error.
.
Language=ChineseSimplified
文件%1包含错误的%2 。
.
Language=ChineseTraditional
文件%1包含錯誤的%2 。
.

MessageId=0x5
Severity=Informational
Facility=System
SymbolicName=MSG_RETRYS
Language=English
There have been %1!d! attempts with %2!d!%% success%! Disconnect from 
the server and try again later.
.
Language=ChineseSimplified
已经尝试%1!d!次,成功率%2!d!%%!断开与服务器的连接,稍后再试。
.
Language=ChineseTraditional
已經嘗試 %1!d!次,成功率%2!d!%%!斷開與服務器的連接,稍後再試。
.

注意: 第4和第5条消息包含了格式化参数(%为格式化参数指示符),有关说明请参考FormatMessage函数。

3. 编译上述messages.mc消息文本文件

使用mc.exe编译器进行编译(该编译器在VC安装目录的Bin子目录内)。

编译命令如下:

mc.exe -u -U messages.mc

编译结果输出5个文件:

messages.h    :消息相关的常量定义
messages.rc   :消息表资源定义
MSG00409.bin  :英文的二进制消息文件
MSG00404.bin  :繁体中文的二进制消息文件
MSG00804.bin  :简体中文的二进制消息文件

4. 编译资源文件(.rc)

资源文件(.rc)的编译设有什么特别的要求,常用的rc.exe资源编译器。

假如你的程序所用的资源文件为rsrc.rc,则将消息表定义信息放到该文件内,如:

// 消息表
#include <messages.h>
#include <messages.rc>

然后运行编译命令:

rc.exe rsrc.rc

这样,你的程序就可以使用消息表资源了。

5. 消息ID和消息代码

使用FormatMessage函数来获取和格式化消息文本时,使用的是消息代码,而不是消息ID。消息代码是由MessageId、Severity和Facility三项组成,如果在消息文本定义中没有指定Severity和Facility。那么消息代码就是消息ID。

如例子中第一条消息定义为:

 MessageId=0x1
 Severity=Error
 Facility=Runtime
 SymbolicName=MSG_BAD_COMMAND
 Language=English
 You have chosen an incorrect command.
.

则该条肖息的代码为: (3 SHL 30) or (2 SHL 16) or 1 =0c0020001h

其中:
3= Severity所指定Error值
2= Facility所指的Runtime值
1= MessageId定义的值

而这个代码值由SymbolicName指定一个别名MSG_BAD_COMMAND,并输出到messages.h文件。如:

#define MSG_BAD_COMMAND ((DWORD)0xC0020001L)

这就是SeverityNames和FacilityNames所表述的意思。

6. 显示消息

在程序中要显示上述定义的5条消息,就要使用FormatMessage函数(有关该API的说明请参考Windows API文档)。以下为示例.

(1) 定义常量。以下常量从生成的messages.h文件中获取。

 MSG_BAD_COMMAND     EQU 0C0020001h
 MSG_BAD_PARM1       EQU 080040002h
 MSG_STRIKE_ANY_KEY  EQU 000000003h
 MSG_CMD_DELETE      EQU 0C0000004h
 MSG_RETRYS          EQU 040000005h

(2) 写一个消息显示函数

;=============================================
;多语言消息表测试
;入 uLang=语言ID。例子中定义了3种语言的消息:
;          409h=英文
;          404h=繁体中文
;          804h=简体中文
;   uMsgId=消息代码:
;          MSG_BAD_COMMAND
;          MSG_BAD_PARM1
;          MSG_STRIKE_ANY_KEY
;          MSG_CMD_DELETE
;          MSG_RETRYS
;   inum=格式化插入参数个数
;       =0: 无格式化插入参数(即消息1-3)
;       =2: 2个格式化插入参数(即消息4、5)
;   s_arg1=格式化插入参数1
;   s_arg2=格式化插入参数2
;=============================================
Message_Show proc uLang:DWORD,uMsgId:DWORD,\
             inum:DWORD,s_arg1:QWORD,s_arg2:QWORD
 LOCAL ss_buf[1024]:BYTE
 invoke GetModuleHandle,0
 mov rdx,rax
 xor r10,r10
 mov ecx,FORMAT_MESSAGE_FROM_HMODULE
 cmp inum,0
 jz ss_1  ;无格式化插入参数
 lea r10,s_arg1
 or ecx,FORMAT_MESSAGE_ARGUMENT_ARRAY
ss_1:
 invoke FormatMessage,ecx,rdx,uMsgId,uLang,ADDR ss_buf,1024,r10
 test eax,eax
 jz ss_0
 invoke MessageBox,NULL,ADDR ss_buf,"Message",MB_OK  ;显示消息
ss_0:
 ret
Message_Show endp
;====================
;消息表测试
;====================
Test_Message proc
;---英语---
 invoke Message_Show,409h,MSG_BAD_COMMAND,0,0,0
 invoke Message_Show,409h,MSG_BAD_PARM1,0,0,0
 invoke Message_Show,409h,MSG_STRIKE_ANY_KEY,0,0,0
 invoke Message_Show,409h,MSG_CMD_DELETE,2,"Test.txt","Syntax"
 invoke Message_Show,409h,MSG_RETRYS,2,10,3
;---简体中文---
 invoke Message_Show,804h,MSG_BAD_COMMAND,0,0,0
 invoke Message_Show,804h,MSG_BAD_PARM1,0,0,0
 invoke Message_Show,804h,MSG_STRIKE_ANY_KEY,0,0,0
 invoke Message_Show,804h,MSG_CMD_DELETE,2,"Test.txt","语法"
 invoke Message_Show,804h,MSG_RETRYS,2,10,3
;---繁体中文---
 invoke Message_Show,404h,MSG_BAD_COMMAND,0,0,0
 invoke Message_Show,404h,MSG_BAD_PARM1,0,0,0
 invoke Message_Show,404h,MSG_STRIKE_ANY_KEY,0,0,0
 invoke Message_Show,404h,MSG_CMD_DELETE,2,"Test.txt","语法"
 invoke Message_Show,404h,MSG_RETRYS,2,10,3
 ret
Test_Message endp

7. 说明

上述例子是将3种语言的消息文本都放在一个程序中,如果要实现本地化,则应按上述方法将3种消息文本放在一个DLL文件中,然后使用UpdateResource函数来实现本地化操作。

有关本地化方法将在后续文章中介绍。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值