Windows 编程/资源脚本参考
本附录页面将尝试列出不同类型的资源,并尝试展示如何使用这些资源。
资源脚本文件是人类可读的文本文件,以 ANSI 或 Unicode(更严格地说,带字节顺序标记 (BOM) 的 UTF-16)格式存储。为了在 ANSI 格式中混合不同语言,存在一个奇特的 #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 取决于您的偏好。旧的、浪费空间的风格是 BEGIN/END 对,来自 MacOS 和 Win16 API 调用的 Pascal 遗产。C 程序员通常更喜欢花括号。Visual Studio 资源编辑器始终生成 BEGIN/END 对,以及大量维护内容。
从一些头文件包含和 #define
语句开始,每个资源都被包含为以下两种形式之一
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
可以是字符串或数字。没有引号!数字是首选的识别方法。所有预定义的资源类型都是数字。但要注意!如果您对资源编译器使用未知 ID(例如 Visual Studio 6 的 MANIFEST),您将不会收到错误或警告,并且资源将以字符串资源类型构建。Windows XP 使用 ExecProcess()
不会找到该预期清单,并且您的程序将以旧的可视化风格显示。
还支持使用 #if / #ifdef / #endif 进行条件编译。
ID 的表达式仅限于非常简单的数学运算,不允许使用布尔运算符。
资源被编译成一个三级目录结构
- 资源类型(MENU、DIALOG 等)
- 资源 ID(在资源类型之前的数字 - 对于 STRINGTABLE,最多 16 个字符串组的 ID)
- 资源语言(当前活动语言,由命令行选项或 LANGUAGE 语句给出;仅限 Win32)
以下数据的具体内容取决于实际的资源类型。通常,它是二进制的。
对任意资源的二进制数据的读取访问使用
FindResource() // get a handle LoadResource() // get the binary size LockResource() // get a pointer; Win32: This is a simple macro, Win16: This is a function call. ... // do something UnlockResource() // Win32: This is a do-nothing macro, Win16: This is a function call. FreeResource() // release
因为位图、图标、光标、对话框、字符串表和菜单资源没有正式文档化,并且解析起来有点困难,所以程序员应该使用专门的资源加载函数来加载这些资源类型。请参阅下面这些类型的描述和示例。
标识符通常以特定方式命名,虽然读者和所有程序员都可以自由更改此命名方案。这只是一个建议。标识符通常以前缀 “ID” 开头,后面跟着一个字母表示标识符的类型
- IDS: 字符串资源
- IDM: 菜单资源
- IDC: 命令标识符
- IDD: 对话框资源
- IDA: 加速键表资源
- IDI: 图标或位图资源
- IDB: 位图资源
- ID: 自定义资源,或不常见的资源类型。
有时,菜单中的命令标识符会以 “IDM_” 前缀开头,以区分来自其他来源的命令。
没有必要使用符号标识符。在某些情况下,标识符会使访问对话框或菜单中按数字排列的控件变得复杂。无论如何,标识符不会帮助非英语程序员阅读软件源代码。数字永远不需要翻译。而且标识符和数字都需要解释。
ID 允许在 0..65535 范围内,并在 1..32767 范围内优先使用。
此关键字具有不同的范围
- 本地(对于一个资源),如果位于资源行下方,例如
21 MENU LANGUAGE 7,1 // or, LANG_GERMAN, SUBLANG_GERMAN { POPUP "&Datei" // = "&File" ...
此语言适用于该菜单
- 全局(对于所有后续资源),如果位于其他位置
与语言无关的资源,如与文化无关的图标、版本信息和清单,应始终设置为 LANGUAGE 0,0(或更详细地说,LANG_NEUTRAL,SUBLANG_NEUTRAL)。
注意始终检查图像是否与文化相关!一个典型的错误是用于主电源供电笔记本电脑的插头符号:它无疑显示了一个美国插头,即使是在欧洲也是如此。显然,包含字母或文本的图像与文化相关。
从 Win16 遗产中有一些内存管理标志,例如 MOVEABLE、FIXED 等。请参阅 LocalAlloc() 了解一些标志。
资源在程序运行时加载到内存中。但是,如果资源没有被使用,并且 Windows 不立即需要它们,资源可以选择从内存中卸载,直到需要为止。要指定可以从内存中卸载未使用的资源,您可以将 DISCARDABLE 关键字与资源一起列出。DISCARDABLE 资源允许更有效地使用内存,但如果需要从磁盘加载它们,则会减慢程序速度。
DISCARDABLE 关键字对于 32 位 Windows 被忽略,但为了兼容性而保留。[1] 32 位资源永远不会被加载,而是被映射到内存中。
图标可以使用 ICON
关键字存储在资源文件中。以下是用资源脚本中使用图标的通用示例
IDI_ICON<n> ICON [DISCARDABLE] "iconfile.ico"
Windows 资源管理器将使用脚本中的第一个图标来显示二进制可执行文件。例如,如果我们加载两个图标,如下所示
IDI_ICON1 ICON DISCARDABLE "icon1.ico" IDI_ICON2 ICON DISCARDABLE "icon2.ico"
并且我们在相应的 resource.h
中这样定义我们的宏
#define IDI_ICON1 1 #define IDI_ICON2 2
可执行文件将以 icon1.ico 作为其图标。
要从可执行模块加载图标,假设我们有一个指向模块的实例句柄(以下示例中的 hInst
),我们可以这样获取指向图标的句柄
HICON hIcon; hIcon = LoadIcon(hInst, MAKEINTRESOURCE(IDI_ICON1));
这将返回指向与标识符 “IDI_ICON1” 关联的图标的句柄。图标标识符通常以 “IDI_” 为前缀,表示 “图标的 ID”。
LoadIcon()
函数的第二个参数是指向字符串的指针。字符串指针是 32 位值。但是,如果最高 16 位全部为零,Windows 将把该值视为资源编号,而不是字符串。为了在字符串和 16 位整数之间进行转换,Microsoft 提供了 MAKEINTRESOURCE 宏。类似地,我们可以使用字符串来定义我们的图标
MYICON1 ICON DISCARDABLE "icon1.ico"
并且我们可以按名称加载此字符串
HICON hIcon; hIcon = LoadIcon(hInst, "MYICON1");
资源的字符串标识符不区分大小写。
WNDCLASSEX
具有指向两个图标的句柄值:一个大图标和一个小图标。小图标是左上角使用的图标。小图标通常为 16 像素见方。大图标通常为 32 像素见方。如果未提供小图标句柄,则大图标将缩小以适合。
如果 LoadIcon()
函数使用 NULL 实例句柄提供,Windows 将提供一个默认图标供使用。
最近,Win32 API 提供了 LoadImage 函数,用于从单个函数加载图标、位图和鼠标光标。您可以在 MSDN 上找到有关此函数的更多信息。
在内部,图标存储在数字资源类型 RT_ICON == 3 下,并分组在 RT_GROUP_ICON == 14 下。
位图可以像资源文件中的图标一样加载。
(bitmap ID or name) BITMAP [DISCARDABLE] "bitmapfile.bmp"
位图可以使用名为 **LoadBitmap** 的函数访问(同样,Win32 API 的新版本更喜欢使用 **LoadImage** 加载位图、图标或光标)。LoadBitmap 返回一个 HBITMAP 句柄类型。
HBITMAP hBmp; hBmp = LoadBitmap(hInst, MAKEINTRESOURCE(IDB_BITMAP1));
或者,如果我们已命名位图资源
hBmp = LoadBitmap(hInst, "MyBitmapRes");
位图是大型资源,如果 Windows 无法将位图加载到内存中(或如果 ID 或名称值无效),该函数将返回 NULL 值。请确保在使用句柄之前测试此值。
必须通过将句柄传递给 DeleteObject() 函数来从内存中卸载位图。您可以在 MSDN 上找到有关此方面的更多信息。
位图标识符通常使用 “IDB_” 前缀,表示它是位图的 ID。
在内部,位图存储在数字资源类型 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
重要的是要注意,在 BEGIN 和 END 关键字的位置,程序员也可以使用更类似于 C 的大括号,如下所示。
STRINGTABLE DISCARDABLE { IDS_STRING1, "This is my first string" IDS_STRING2, "This is my second string" ... }
有些人更喜欢其中一个,但它们对资源编译器来说都是一样的。
可以使用 **LoadString** 函数加载字符串。LoadString 比 LoadBitmap 或 LoadIcon 函数更复杂。
int LoadString(HINSTANCE hInstance, UINT uID, LPTSTR lpBuffer, int nBufferMax);
hInstance 参数,如我们所知,是包含字符串的模块的实例句柄。uID 参数包含我们尝试访问的字符串编号。lpBuffer 是将接收字符串的字符数组变量,而 nBufferMax 编号告诉 Windows 可以加载的最大字符数。此计数是安全预防措施,因此请确保不要允许 Windows 将字符数据写入字符串末尾以外的位置。MSDN 在此函数的页面上显示了一个很大的警告,程序员必须注意此警告。 msdn
Windows 将在字符串写入缓冲区后自动以零结尾。LoadString 将返回实际写入字符串中的字符数,以防字符数少于允许的最大字符数。如果此返回值为 0,则字符串资源不存在或无法加载。
字符串可以在中间包含 “\0”。由于字符串保存为计数字符串,因此 LoadString 返回保存的字符数,包括中间的零。但是,大多数资源编辑器都会在处理此类字符串时失败。
在内部,字符串表存储在数字资源类型 RT_STRING == 6 下,最多分组为 16 个相邻 ID。
键盘加速键是几乎每个 Windows 应用程序的常见部分,因此,最好通过将它们放入资源脚本中来简化创建加速键的工作。以下是如何创建加速键表。
(Accelerator Table ID or name) ACCELERATORS [DISCARDABLE] BEGIN (key combination), (Command ID) ... END
键组合使用字符串文字字符(例如,“A”)或虚拟键代码值来指定。以下是一些示例。
IDA_ACCEL_TABLE ACCELERATORS DISCARDABLE BEGIN "A", IDA_ACTION_A //Shift+A END
现在,当按下 “Shift+A” 键组合时,您的窗口过程将收到 WM_COMMAND 消息,消息的 WPARAM 字段中将包含值 IDA_ACTION_A。
如果我们想使用 “Alt” 键或 “Ctrl” 键的组合,我们可以分别使用 ALT 和 CONTROL 关键字。
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
此外,我们可以使用 “^” 符号来表示 CONTROL 键代码。
IDA_ACCEL_TABLE ACCELERATORS DISCARDABLE BEGIN "^a", IDA_ACTION_A //Control+A END
同样,如果我们想成为超级黑客,我们可以直接使用 ASCII 代码。
IDA_ACCEL_TABLE ACCELERATORS DISCARDABLE BEGIN 65, IDA_ACTION_A, ASCII //65 = "A", Shift+A END
或者,我们可以使用虚拟键代码标识符来引用键(包括非字母数字键),方法是使用 VIRTKEY 标识符。
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
现在,如果我们使加速键与菜单命令对应,当我们按下加速键时,菜单命令将被点亮。也就是说,除非我们在加速键表中指定 “NOINVERT” 关键字,否则菜单将被点亮。
IDA_ACCEL_TABLE ACCELERATORS DISCARDABLE BEGIN "A", IDA_ACTION_A, NOINVERT //Shift+A (non inverted menu selection) END
要加载加速键表,我们需要使用 **LoadAccelerators** 函数,如下所示。
HACCEL hAccel; hAccel = LoadAccelerators(hInst, MAKEINTRESOURCE(IDA_ACCEL_TABLE));
同样,我们也可以为资源指定字符串名称,并使用该字符串来加载该表。
使用加速键时,我们需要修改消息循环以拦截按键消息,并根据加速键表规则将它们转换为命令消息。我们使用 **TranslateAccelerator** 函数来拦截按键消息,并将它们转换为命令消息,如下所示。
while ( (Result = GetMessage(&msg, NULL, 0, 0)) != 0) { if (Result == -1) { // error handling } else { if (!TranslateAccelerator(hwnd, haccel, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } } }
此外,如果我们正在编写 MDI 应用程序,我们需要拦截子窗口的加速键消息,我们还需要使用 **TranslateMDISysAccel** 函数。
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); } } }
其中 “hwndFrame” 是框架窗口的句柄,而 “hwndClient” 是 MDI 客户端窗口的句柄。
在内部,加速键存储在数字资源类型 RT_ACCELERATOR == 9 下。
可以使用 MENU 关键字在资源脚本中定义菜单。菜单中包含两种类型的项目,即顶级 “POPUP” 菜单项目和二级 “MENUITEM” 项目。它们在菜单中的定义方式如下。
(ID or name) MENU [DISCARDABLE] BEGIN POPUP "File" POPUP "Edit" BEGIN MENUITEM "Copy", IDM_EDIT_COPY MENUITEM "Paste", IDM_EDIT_PASTE END ... END
我们在此处包含了一些示例,以便您可以看到 POPUP 和 MENUITEM 之间的区别。当我们有一个 ID 为 ID_MENU 的菜单时,我们可以像这样将其加载到程序中。
HMENU hmenu; hmenu = LoadMenu(hInst, MAKEINTRESOURCE(ID_MENU));
获得此句柄后,我们可以将其传递给 CreateWindow 函数,并将其应用于我们的窗口。
当选择菜单项时,主机程序会收到 WM_COMMAND 消息,WPARAM 参数中包含菜单项标识符。如果我们有一个基本的窗口过程 switch-case 语句,我们可以看到如下所示。
case WM_COMMAND: switch(WPARAM) { case IDM_EDIT_COPY: //handle this action break; case IDM_EDIT_PASTE: //handle this action break; } break;
在菜单中,如果我们想将菜单项与加速键关联,我们可以将其定义如下。
ID_MENU MENU DISCARDABLE BEGIN POPUP "File" POPUP "Edit" BEGIN MENUITEM "&Copy", IDM_EDIT_COPY MENUITEM "&Paste", IDM_EDIT_PASTE END ... END
请注意,我们如何在 “Copy” 中的 “C” 和 “Paste” 中的 “P” 前面放置了和号 (&)。这意味着这些字母将被下划线,但更重要的是,如果按下了加速键组合,菜单中的这些项将被突出显示(除非加速键表中指定了 NOINVERT 标签)。如果在 POPUP 菜单项前面放置了一个和号,则按 ALT+该字母将弹出该菜单。例如,让我们定义我们的菜单。
ID_MENU MENU DISCARDABLE BEGIN POPUP "&File" POPUP "&Edit" BEGIN MENUITEM "Copy", IDM_EDIT_COPY MENUITEM "Paste", IDM_EDIT_PASTE END ... END
现在,如果我们按下 ALT+F,我们将打开 File 菜单,如果我们按下 ALT+E,它将打开 Edit 菜单。对于只输入一个额外字符,这真是很不错的功能。
在内部,菜单存储在数字资源类型 RT_MENU == 4 下。
程序可以在资源脚本中包含有关其版本及其作者的某些信息。此版本信息会在您在 Windows 中右键单击可执行文件并单击 “属性” 时显示。在属性对话框中,此信息显示在 “版本” 选项卡上。
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 VALUE "Translation", 0x409, 1252 END
在内部,VersionInfo 存储在数字资源类型 RT_VERSION == 16 下。它是在 Windows 3 中引入的。
对话框资源遵循一般模式。
(Dialog ID or name) DIALOG [DISCARDABLE] x, y, width, height TITLE "(dialog box title)" [CLASS "(class name)"] FONT "(font name)" BEGIN ... END
如果对话框没有与类关联,则无需填写 CLASS 字段。所有列为用引号括起来的字符串都 *必须用资源脚本中的引号括起来*,否则会发生错误。然后,对话框中的各个项目在 BEGIN 和 END 标签之间指定。
在内部,对话框存储在数字资源类型 RT_DIALOG == 5 下。
CONTROL classname,windowname,id,left,top,width,height,windowflags
清单资源包含使用 UTF-8 编码的 XML 描述文件,用于描述操作系统和 DLL 依赖关系。通常,该资源指示使用 Windows XP 版本 6.0 的 comctl32.dll,以对标准和通用控件使用 Luna 样式。
在内部,清单存储在数字资源类型 RT_MANIFEST == 24 下。它是在 Windows 4.1 中引入的。
用户类型资源应使用一些更大的资源类型标识符,或 RT_RCDATA == 10。