Windows Programming/资源脚本参考
本篇附录介绍资源脚本文件(Resource script file)。[1]
一般结构
[编辑]资源脚本文件是人可读的文本文件,用ANSI或UTF-16小端序带字节掩码“BOM”格式。
为支持多语种国际化,对ASNI格式,使用#pragma
切换代码页。对Unicode格式使用#pragma
切换,而LANGUAGE
仅支持Win32。
典型的小文件例子:
#include <windows.h> #define IDC_STATIC -1 100 ICON "ProgIcon.ico" 10 MENU { // or BEGIN POPUP "&File" { MENUITEM "&Exit",IDCANCEL } } // or END
使用花括号或BEGIN/END都行。Visual Studio资源编辑器总是产生BEGIN/END对。
各种资源的格式为:
id_of_resource resource_type [memory management flags] "filename"
或
id_of_resource resource_type [memory management flags] BEGIN subsequent data END
上述规则的例外情形:
LANGUAGE
语句可以放在任何位置(仅限Win32)DIALOG
与VERSIONINFO
资源,在头行与BEGIN之间还有别的语句。STRINGTABLE
资源在关键字之前不需要资源ID,但每个资源需要ID前缀
id_of_resource
与resource_type
可以是字符串或数。这里不需要引号。数是更好的辨识资源的方法。所有预定的资源的类型都是数。
支持条件预编译指令 #if / #ifdef / #endif
用于ID的表达式限于非常简单的数学,不允许布尔运算符。
内部机制
[编辑]资源被编译为三级目录结构:
- 资源类型 (MENU, DIALOG 等)
- 资源ID
- 资源所用的语言
读二进制资源的API函数使用顺序示例:
FindResource() // get a handle LoadResource() // get the binary size LockResource() // get a pointer; Win32: This is a simple macro … // do something UnlockResource() // Win32: This is a do-nothing macro FreeResource() // release
由于bitmap, icon, cursor, dialog, string table, menu资源没有官方文档,分析时有点费解,编程者应该使用专门的资源类型的加载函数。详见下述。
标识符
[编辑]建议以ID为开头,第三个字母表示资源类型:
- IDS: A string resource
- IDM: A menu resource
- IDC: A command identifier
- IDD: A dialog box resource
- IDA: An Accelerator table resource
- IDI: An Icon or bitmap resource
- IDB: A Bitmap resource
- ID: A custom resource, or an uncommon resource type.
有时,菜单中的命令的标识符使用前缀"IDM_",以区分其它资源的命令。
ID允许范围为0..65535,建议范围1..32767
LANGUAGE
[编辑]关键词LANGUAGE有不同的作用域:
- 本地(用于一个资源)如果在资源行之下,例如:
21 MENU LANGUAGE 7,1 // or, LANG_GERMAN, SUBLANG_GERMAN { POPUP "&Datei" // = "&File" …
- 全局(适用于所有之下的资源)如果出现在别处:
语言中立资源,如无文化局限的icons, VersionInfo, Manifests 应当设置LANGUAGE 0,0 (或更繁琐一点 LANG_NETUTRAL,SUBLANG_NEUTRAL)
在一份RC文件中,同一个资源ID在不同语言下可出现多次。
内存管理标志
[编辑]Win16时代留下了一些内存管理标志,如MOVEABLE, FIXED等。详见LocalAlloc()
DISCARDABLE
[编辑]关键字DISCARDABLE在32位Windows上被忽略。用于向后兼容。[1]
Icons
[编辑]操作系统使用icon在用户界面种表示对象如文件、文件夹、快捷、应用程序、文档等。 操作系统提供了一套标准的icon,在SDK头文件种以IDI_为前缀定义了其标识符。
Icons在资源文件中用ICON
关键字声明。例如:
IDI_ICON<n> ICON [DISCARDABLE] "iconfile.ico"
Windows Explorer使用资源脚本中第一个icon显示二进制可执行文件。例如,如果有2各icon:
IDI_ICON1 ICON DISCARDABLE "icon1.ico" IDI_ICON2 ICON DISCARDABLE "icon2.ico"
在对应的resource.h
中定义宏:
#define IDI_ICON1 1 #define IDI_ICON2 2
那么可执行文件以icon1.ico作为icon。
为可执行模块加载一个icon,假定我们有该实例的句柄(下例hInst
),可以得到icon的句柄:
HICON hIcon; hIcon = LoadIcon(hInst, MAKEINTRESOURCE(IDI_ICON1));
Icon辨识符一般前缀为"IDI_"表示"ID for an Icon"。
LoadIcon()
函数的第二个参数是一个字符串指针。如果该指针的高16位是零,Windows把它当作一个资源数的值,而不是一个字符串。Microsoft提供了宏MAKEINTRESOURCE把16位无符号数转为字符串指针。当然也可以用字符串来定义一个Icon:
MYICON1 ICON DISCARDABLE "icon1.ico"
可以用名字加载icon:
HICON hIcon; hIcon = LoadIcon(hInst, "MYICON1");
资源的字符串标识符是大小写敏感的。
WNDCLASSEX
有2个句柄值用于表示2个icon:大icon与小icon。小icon用于应用程序左上角,一般是16个像素正方形。大icon是32个像素正方形。如果不提供小icon,自动从大icon产生小icon。
LoadIcon()
函数如果使用NULL实例句柄,则Windows提供缺省icon。
Win32 API已经允许用LoadImage函数加载icon、bitmap、鼠标光标。
二进制可执行文件内部用数值资源类型RT_ICON == 3存储只有单一图片的icon,用RT_GROUP_ICON == 14存储有一组图片的icon。
Windows使用4种icon尺寸:system small、system large、shell small、shell large。
- system small icon:在窗口标题条上显示。
int cx = GetSystemMetrics(SM_CXSMICON ); int cy = GetSystemMetrics(SM_CYSMICON );
可获取其值。一般是16X16。 - system large icon:主要用于应用程序,也在Alt+Tab对话框显示。其尺寸不可更改。
int cx = GetSystemMetrics(SM_CXICON ); int cy = GetSystemMetrics(SM_CYICON );
可获取其值。一般是32X32。 - shell small icon:用于Windows Explorer与common dialog。默认为system small的尺寸。用 SHGetFileInfo函数与ImageList_GetIconSize函数查询其尺寸。
- shell large icon:用于桌面。用 SHGetFileInfo函数与ImageList_GetIconSize函数查询其尺寸。
开始菜单使用shell small icons或shell large icons,取决于是否“Use large icons check box”被选中。
应用程序应该在资源中提供下述尺寸的icon:
- 48x48, 256 color
- 32x32, 16 color
- 16x16 pixels, 16 color
位图
[编辑]资源文件中,位图定义为:
(bitmap ID or name) BITMAP [DISCARDABLE] "bitmapfile.bmp"
加载位图使用LoadBitmap函数(也可以用LoadImage函数):
HBITMAP hBmp; hBmp = LoadBitmap(hInst, MAKEINTRESOURCE(IDB_BITMAP1));
或者加载用字符串来命名的位图资源:
hBmp = LoadBitmap(hInst, "MyBitmapRes");
位图是大型资源,如果加载到内存失败或者ID或名字值无效,该函数返回值为NULL。因此,使用前应该检查其句柄是否为空。
卸载位图用函数DeleteObject函数。
位图标识符一般以"IDB_"为前缀。
可执行模块内部,位图的数值资源类型为RT_BITMAP == 2。
鼠标光标
[编辑]加载鼠标光标使用LoadCursor函数。
可执行模块内部,鼠标光标的资源数值类型,当为单独图像为 RT_CURSOR == 1,一组图像时为 RT_GROUP_CURSOR == 12。
除了明确指出外部的二进制资源的文件,资源编译器允许资源文件内联的二进制数据。例如:
42 ICON { 123,4567,0x89AB,0xCDEF '\x01','\x23',"ajx" }
源自Win16的遗产:
- 十进制或十六进制的数作为未对齐的16位小尾值。
- 字符作为8位值存储。
字符串表
[编辑]一个资源脚本文件中可以有多个字符串表。编译时会自动合并为一个。例子:
STRINGTABLE DISCARDABLE BEGIN IDS_STRING1, "This is my first string" IDS_STRING2, "This is my second string" ... END
使用LoadString函数装入字符串:
int LoadString(HINSTANCE hInstance, UINT uID, LPTSTR lpBuffer, int nBufferMax);
注意该函数可能会返回丰富的警告信息。msdn Windows会在返回的字符串自动以0字节结尾。
加速键
[编辑]Keyboard accelerators are a common part of nearly every windows application, and therefore it is a good idea to simplify the job of creating accelerators by putting them in a resource script. Here is how to create an accelerator table:
(Accelerator Table ID or name) ACCELERATORS [DISCARDABLE] BEGIN (key combination), (Command ID) ... END
Key combinations are specified in terms of either a string literal character ("A" for instance) or a virtual key code value. Here are some examples:
IDA_ACCEL_TABLE ACCELERATORS DISCARDABLE BEGIN "A", IDA_ACTION_A //Shift+A END
Now, when the key combination "Shift+A" is pressed, your window procedure will receive a WM_COMMAND message with the value IDA_ACTION_A in the WPARAM field of the message.
If we want to use combinations of the "Alt" key, or the "Ctrl" key, we can use the ALT and CONTROL keywords, respectively:
IDA_ACCEL_TABLE ACCELERATORS DISCARDABLE BEGIN "a", IDA_ACTION_A, ALT //Alt+A "b", IDA_ACTION_B, CONTROL //Ctrl+B "c", IDA_ACTION_C, ALT, CONTROL //Alt+Ctrl+A END
Also, we can use the "^" symbol to denote a CONTROL key code:
IDA_ACCEL_TABLE ACCELERATORS DISCARDABLE BEGIN "^a", IDA_ACTION_A //Control+A END
Similarly, if we want to be super hackers, would could use the ASCII code directly:
IDA_ACCEL_TABLE ACCELERATORS DISCARDABLE BEGIN 65, IDA_ACTION_A, ASCII //65 = "A", Shift+A END
Or, we could refer to keys (including non-alphanumeric keys) with their Virtual Key Code identifiers, by using the VIRTKEY identifier:
IDA_ACCEL_TABLE ACCELERATORS DISCARDABLE BEGIN VK_F12, IDA_ACTION_F12, VIRTKEY //press the "F12 Key" VK_DELETE, IDA_ACTION_DEL, VIRTKEY, CONTROL //Ctrl+Delete END
Now, If we make an accelerator correspond to a menu command, the menu command will light up when we press the accelerator. That is, the menu will light up unless we specify the "NOINVERT" keyword:
IDA_ACCEL_TABLE ACCELERATORS DISCARDABLE BEGIN "A", IDA_ACTION_A, NOINVERT //Shift+A (non inverted menu selection) END
To Load an accelerator table, we need to use the LoadAccelerators function, as such:
HACCEL hAccel; hAccel = LoadAccelerators(hInst, MAKEINTRESOURCE(IDA_ACCEL_TABLE));
Again, we could have given our resource a string name, and used that string to load the table.
When using accelerators, we need to alter our message loop to intercept the keypress messages, and translate them into command messages according to our accelerator table rules. We use the TranslateAccelerator function, to intercept the keypress messages, and translate them into command messages, as such:
while ( (Result = GetMessage(&msg, NULL, 0, 0)) != 0) { if (Result == -1) { // error handling } else { if (!TranslateAccelerator(hwnd, haccel, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } } }
Also, if we are writing an MDI application, we need to intercept Accelerator messages from the child windows, we use the TranslateMDISysAccel function also:
while ( (Result = GetMessage(&msg, NULL, 0, 0)) != 0) { if (Result == -1) { // error handling } else { if ( !TranslateMDISysAccel(hwndClient, &msg) && !TranslateAccelerator(hwndFrame, haccel, &msg) ) { TranslateMessage(&msg); DispatchMessage(&msg); } } }
Where "hwndFrame" is the handle to the frame window, and "hwndClient" is the handle to the MDI client window.
Internally, Accelerators are stored under numeric resource type RT_ACCELERATOR == 9.
菜单
[编辑]Menus can be defined in a resource script using the MENU keyword. There are 2 types of items that appear in a menu, the top level "POPUP" menu items, and the secondary "MENUITEM" items. These are defined in a menu as such:
(ID or name) MENU [DISCARDABLE] BEGIN POPUP "File" POPUP "Edit" BEGIN MENUITEM "Copy", IDM_EDIT_COPY MENUITEM "Paste", IDM_EDIT_PASTE END ... END
We have included a few examples here, so that you can see the difference between a POPUP and a MENUITEM. When we have a menu with the ID_MENU identifier, we can load it into our program as such:
HMENU hmenu; hmenu = LoadMenu(hInst, MAKEINTRESOURCE(ID_MENU));
Once we have this handle, we can pass it to the CreateWindow function, and apply it to our window.
When a menu item is selected, the host program receives a WM_COMMAND message, with the menu item identifier in the WPARAM parameter. If we have a basic window procedure switch-case statement, we can see this as follows:
case WM_COMMAND: switch(WPARAM) { case IDM_EDIT_COPY: //handle this action break; case IDM_EDIT_PASTE: //handle this action break; } break;
In a menu, if we want to associate a menu item with an accelerator, we can define it as such:
ID_MENU MENU DISCARDABLE BEGIN POPUP "File" POPUP "Edit" BEGIN MENUITEM "&Copy", IDM_EDIT_COPY MENUITEM "&Paste", IDM_EDIT_PASTE END ... END
Notice how we put the ampersand (&) in front of the "C" in "Copy" and the "P" in "Paste". This means that those letters will be underlined, but more importantly, if an accelerator key combination is pressed, those items in the menu will be highlighted (unless the NOINVERT tag is specified in the accelerator table). If an ampersand is placed before a POPUP menu item, pressing ALT+ that letter will popup that menu. For instance, lets define our menu:
ID_MENU MENU DISCARDABLE BEGIN POPUP "&File" POPUP "&Edit" BEGIN MENUITEM "Copy", IDM_EDIT_COPY MENUITEM "Paste", IDM_EDIT_PASTE END ... END
Now, if we press ALT+F, we will pop open the File menu, and if we press ALT+E it will open the Edit menu. That's pretty nice functionality for only a single extra character to type.
Internally, Menus are stored under numeric resource type RT_MENU == 4.
版本信息
[编辑]名称 | 描述 |
---|---|
CompanyName | 生成文件的公司,例如 "Microsoft Corporation" 或 "Standard Microsystems Corporation,Inc." 此字符串是必需的。 |
FileDescription | 要向用户显示的文件说明。 当用户选择要安装的文件时,此字符串可能会显示在列表框中,例如 "AT-Style 键盘的键盘驱动程序"。 此字符串是必需的。 |
FileVersion | 文件的版本号,例如 "3.10" 或 "5.00. RC2"。 此字符串是必需的。 |
InternalName | 文件的内部名称(如果存在)(例如,如果文件为动态链接库,则为模块名称)。 如果该文件没有内部名称,则此字符串应为原始文件名,而不包含扩展名。 此字符串是必需的。 |
LegalCopyright | 适用于该文件的版权声明。 这应包括所有声明的完整文本、合法符号、版权日期等。 此字符串是可选的。 |
LegalTrademarks | 适用于该文件的商标和注册商标。 这应包括所有声明的完整文本、合法符号、商标号等。 此字符串是可选的。 |
OriginalFilename | 文件的原始名称,不包括路径。 此信息使应用程序能够确定文件是否已被用户重命名。 名称的格式取决于为其创建该文件的文件系统。 此字符串是必需的。 |
PrivateBuild | 有关文件私有版本的信息(例如,"由 TESTER1 在 TESTBED 上生成 \ ")。 仅当在根块的 fileflags 参数中指定 VS _ FF _ PRIVATEBUILD 时,才应提供此字符串。 |
ProductName | 用于分发文件的产品的名称。 此字符串是必需的。 |
ProductVersion | 用于分发该文件的产品的版本,例如 "3.10" 或 "5.00. RC2"。 此字符串是必需的。 |
SpecialBuild | 指示此版本的文件与标准版本的不同之处的文本,例如 "用于 TESTER1 的专用生成解决 M250 和 M250E 计算机上的鼠标问题"。 仅当在根块的 fileflags 参数中指定 VS _ FF _ SPECIALBUILD 时,才应提供此字符串。 |
Comments | 出于诊断目的应显示的其他信息 |
例如:
BLOCK "StringFileInfo" BEGIN BLOCK "040904E4" BEGIN VALUE "CompanyName", "My Company.\0" VALUE "FileDescription", "A Win32 program." VALUE "FileVersion", "1.0.0.0\0" VALUE "ProductName", "The product name.\0" VALUE "ProductVersion", "1.0\0" VALUE "LegalCopyright", "My Company.\0" END END BLOCK "VarFileInfo" BEGIN /* 只能有一行,但可以包含一对或多对WORD */ VALUE "Translation", 0x409, 1252 END
对话框
[编辑]对话框资源的通用模式:
(Dialog ID or name) DIALOG [DISCARDABLE] x, y, width, height TITLE "(dialog box title)" [CLASS "(class name)"] FONT "(font name)" BEGIN ... END
如果一个对话框没有关联一个CLASS,那么CLASS域不需要填写。所有字符串必须用双引号包含。
一般控件
[编辑]CONTROL classname,windowname,id,left,top,width,height,windowflags
特殊按钮
[编辑]编辑框
[编辑]Manifests
[编辑]Manifest资源包含UTF-8编码的XML描述信息,关于操作系统与DLL依赖。
FONT
[编辑]FONTDIR
[编辑]RCDATA
[编辑]MESSAGETABLE
[编辑]定义应用程序的消息表资源的ID与文件。消息表是特殊的字符串资源用于event logging以及FormatMessage函数。文件中包含消息编译器MC.EXE产生的二进制消息表。[2]
输入给消息编译器MC.EXE的message text (.mc) 文件采用语法见[3],例子:
; // ***** Sample.mc *****
; // This is the header section.
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
)
LanguageNames=(English=0x409:MSG00409)
LanguageNames=(Japanese=0x411:MSG00411)
; // The following are message definitions.
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>
.
MessageId=0x2
Severity=Warning
Facility=Io
SymbolicName=MSG_BAD_PARM1
Language=English
Cannot reconnect to the server.
.
Language=Japanese
<Japanese message string goes here>
.
MessageId=0x3
Severity=Success
Facility=System
SymbolicName=MSG_STRIKE_ANY_KEY
Language=English
Press any key to continue . . . %0
.
Language=Japanese
<Japanese message string goes here>
.
MessageId=0x4
Severity=Error
Facility=System
SymbolicName=MSG_CMD_DELETE
Language=English
File %1 contains %2 which is in error.
.
Language=Japanese
<Japanese message string goes here>
.
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=Japanese
<Japanese message string goes here>
.
TEXTINCLUDE
[编辑]TEXTINCLUDE是一种资源类型。目的是安全地存储Set Include information,使Visual C++的Set Includes对话框可以表达它们。[4]
Visual C++识别3种特定的TEXTINCLUDE资源,其起源标识数分别是1, 2, 3:
TEXTINCLUDE resource ID | Type of Set Includes information |
---|---|
1 | Symbol Header File |
2 | Read-Only Symbol Directives |
3 | Compile-Time Directives |
用户定义资源
[编辑]用户定义资源应当使用更大的资源类型标识符,如RT_RCDATA == 10.
DLGINCLUDE
[编辑]包含菜单、对话框的#define语句的头文件。资源编辑器使用。
多语言的资源
[编辑]在单个exe文件中可嵌入多种语言的资源。但Visual Studio资源编辑器不支持,所以必须手工编辑。
使用.rc2文件定义资源,因为Visual Studio资源编辑器不会修改它。需要把.rc2存为UTF-16 LE编码并且以空行结束。
一个MFC项目创建时就有了一个空的.rc2文件。我们称它为"main" .rc2文件。在main .rc2文件中,对每种语言增加一行#include该语言的.rc2文件:
#include "lang_en.rc2" #include "lang_de.rc2"
// Restore default language for resources included after current file LANGUAGE LANG_ENGLISH, SUBLANG_NEUTRAL
创建语言相关的.rc2文件。每个文件以该语言的声明 LANGUAGE <LANGID>, <SUBLANGID> 开始,如lang_en.rc2文件中:
LANGUAGE LANG_ENGLISH, SUBLANG_NEUTRAL STRINGTABLE BEGIN IDS_STRING1 "Stack Overflow" IDS_STRING2 "Stack Overflow is a privately held website, the flagship site of the Stack Exchange Network, created in 2008 by Jeff Atwood and Joel Spolsky." END
在文件lang_de.rc2中:
LANGUAGE LANG_GERMAN, SUBLANG_NEUTRAL STRINGTABLE BEGIN IDS_STRING1 "Stapelüberlauf" IDS_STRING2 "Stack Overflow (englisch für Stapelüberlauf) ist eine Internetplattform, auf der angemeldete Benutzer Fragen zum Thema Softwareentwicklung stellen können." END
编译可执行文件并在资源编辑器中检查已经包括了多种语言。也可以在Visual Studio打开.exe程序,查看它的资源。
在源代码中,可以正常加载资源,Windows自动根据当前用户locale加载对应语言的资源。如果没有匹配的资源,它会加载英语资源。
也可以用FindResourceEx函数加载明确给出的某种语言的资源,包括标准MFC资源 afxres.h 。
标准MFC资源 如afxres,可以包含在特点语言的.rc2文件中,如:
LANGUAGE LANG_GERMAN, SUBLANG_NEUTRAL
#ifdef __AFXRES_RC__ #undef __AFXRES_RC__ // To be able to include multiple language versions of afxres.rc #endif #include "l.deu\afxres.rc" // Standard MFC resources
STRINGTABLE BEGIN IDS_STRING1 "Stapelüberlauf" IDS_STRING2 "Stack Overflow (englisch für Stapelüberlauf) ist eine Internetplattform, auf der angemeldete Benutzer Fragen zum Thema Softwareentwicklung stellen können." END