文章出处:http://blog.csdn.net/heyang22118952/archive/2007/02/17/1511433.aspx
本想在月底前翻译完的,谁知翻译的这么快,现在我把它全部贴出来,方便大家共同学习研究Windows资源格式。翻译的不好,也没有经过审校,不当之处请大家指正。
Win32 二进制资源格式
作者:Floyd Rogers
翻译:和阳阳
前言
本文档由微软开发者技术支持(Microsoft Developer Support)编辑并发布。它描述了Win32资源的二进制格式。我们认为这可能对程序开发工作有所帮助,因此提供了本信息。不幸的是在Windows NT 最终版发布前本文档提供的信息可能会有所改变,微软并不会因为发布了本文档就负责保持本资源格式。任何关于此信息的后续问题将会在CompuServe MSWIN32论坛的第四区发布。
——Steve Firebaugh
微软开发者技术支持
1.概述
1.1 与Windows 16(Win 3.0/3.1) 对照比较
1.2 UNICODE字符串
1.3 双字(DWORD)对齐
2.总体信息
2.1 新语句
2.1.1 新按钮(Button)语句
2.1.1.1 AUTO3STATE
2.1.1.2 AUTOCHECKBOX
2.1.1.3 AUTORADIOBUTTON
2.1.1.4 PUSHBOX
2.1.1.5 STATE3(3STATE)
2.1.1.6 USERBUTTON
2.1.2 EXSTYLE语句
2.1.3 CHARACTERISTICS语句
2.1.4 VERSION语句
2.1.5 LANGUAGE语句
2.1.6 MESSAGETABLE语句
2.1.7 对UNICODE字符串的附加语法
3.资源头格式
3.1 DataSize
3.2 HeaderSize
3.3 Type
3.4 Names
3.5 附加资源头信息
3.5.1 DataVersion
3.5.2 MemoryFlags
3.5.3 LanguageId
3.5.4 Version与Characteristics
3.6 区分16位与32位资源文件
3.7 文件对齐
4.资源数据格式
4.1 版本资源
4.2 图标资源
4.3 菜单资源
4.4 对话框资源
4.5 光标资源
4.6 位图资源
4.7 字体和字体目录资源
4.8 字符串表资源
4.9 加速键表资源
4.10 用户定义的资源与RCDATA
4.11 名字表与错误表资源
4.12 版本资源
4.13 消息表(Messagetable)资源
5.修改日志
1.概述
本文档详细描述了Windows 32 API(Windows NT 3.1 和 Win32s)的二进制资源文件(.res)格式的结构。它与现有的Windows 16(Win 3.0/3.1)结构基本相似,但支持了更多更好的新特性,比如UNICODE字符串、版本头和双字对齐等等。因此若想支持这些新特性,资源编译器所生成的文件格式必须相对于Windows 16 有所改变。
1.1 Windows 3.0/3.1 与 Windows 32 对比
Windows 16资源文件包含了一个以上的二进制资源。每个资源都前置一个可变长度的数据结构,它包含:类型、名称、标志和大小。类型与名称域包含一个标识此类型的字符串或一个标识此资源ID的单字值。标志域告诉系统应当如何将此资源载入内存,大小域指出了资源的大小,以字节为单位。因此大小域实际上就是一个指向文件中下一个资源的指针。
Windows 32(NT和Win32s)资源文件继承了此节构,并扩充了头信息,增加了几个域。而且给一些预定义资源(如菜单和对话框)增加了一些域,并使这些结构能够单字或双字对齐,以及增加了UNICODE(16位字符)支持。
还有一个不同点,不过它没什么重要性。它不直接影响资源文件的结构,不过它影响了资源文件是如何合并在可执行文件中的方法。Windows NT使用COFF格式对象。因此,实际上Windows 32可执行文件格式与Windows 16大相径庭,SDK提供了一个转换工具:CVTRES,用来将一个资源文件转换为COFF对象。连接器会直接将这个对象合并到最终的可执行文件中去。和Windows 16一样,不能仅通过多次运行资源编译器来更新资源:必须重新进行连接。
不过Windows 32 API 提供了一些API,使程序可以枚举可执行文件内的所有资源,并可以单独更新其中的资源。
1.2 UNICODE字符串
资源文件中的所有字符串现在都存储为UNICODE格式。在这种格式下,所有的字符都由一个16位(单字)值表示。最开始的128个字符与Windows ANSI字符集完全相同(但这些字符都以16位格式存在,而不是8位)。第160-254个字符与标准Windows字符集相同(注意:第128-159个字符是非法Unicode代码点(codepoint))。这意味着字符串将以UNICODE_NULL结束,而不是单独一个NULL。资源编译器调用Windows API 中的MultiByteToWideChar函数将ASCII字符串转换位UNICODE字符串。所有溢出(escaped)的字符都被当作合法UNICODE字符直接存储。当这些字符串被程序以ASCII字符读出(例如使用LoadString API)时,系统将把它们再由UNICDOE转换为ASCII字符。
仅有的例外是在RCDATA语句中的字符串。这些“伪”字符串并不是真正的字符串,只被当作一些字节的集合。用户可能会用RCDATA语句存储一些自定义的数据结构,认为确定的数据会被存储在确定的位移。如果一个“伪”字符串被自动转换为UNICODE字符串存储起来,可想而知会发生什么样的事情。因此这些“伪”字符串必须以它的本来面目存储下来:ASCII字节。若想在RCDATA语句中包含UNICODE字符串,用户可以使用L前缀的字符串。
1.3 双字(DWORD)对齐
为使二进制资源文件更容易读写,在Windows 32下,文件中的所有对象都是双字对齐的。包括头信息(headers)和数据项(data entries)。这并不会改变资源数据结构中域的顺序,但会在这些域中间增加一些填充域。
font和fontdir结构是仅有的一个例外。因为这两个结构是直接拷贝自别的文件的,它们并不被RC(Resource Compiler,资源编译器)所使用。
2.一般信息
资源编译器通过分析资源描述文件(.rc)、包含任何其它的资源数据文件(如.ICO图标,.CUR光标,.BMP位图,.FNT字体文件等)来创建资源文件。资源文件包含了在可执行文件中创建资源表的所有必须的信息。资源文件的主要目的是为了加速“编辑-编译-链接”循环,因为它使得资源不必被重新编译。
当前存在大约十几种预定义资源类型。包括菜单、对话框、加速键、字符串、图标、光标、位图、字体和版本信息。Windows系统使用这些资源定义应用程序窗口的显示。资源脚本使得程序编制者可以以一种简易的可编辑形式来表现这些特性。其它类型被保留作应用程序自定义的数据所使用。资源编译器不会尝试修改这些用户自定义的数据(比如将16位数据转换为32位数据)。
Windows 32可执行文件并不是一个分段的映像。在16位可执行文件中,每个资源都被单独放在一个段内。Windows 32可执行文件将所有资源统一放在一个对象或区域中。Windows 32可执行文件同时提供一个二进制排序的资源表以允许快速查询一个特定的资源,而不是像16位可执行文件那样只提供一个仅可进行线性查询的表。正因如此,Windows 32可执行文件更加复杂,很难直接对其进行更新,因此Windows 32 API提供了一些函数用来直接修改资源数据。
能够将资源文件转换为一个COFF对象的CVTRES转换工具会创建一个资源表。这个表包含三个目录,以类型、名称、语言为索引顺序。类型和名称目录均由两部分组成:一部分是类型或名称的字符串标识,另一部分是它们的单字值标识。由于类型或名称的字符串标识会比单字值标识占用更多的空间,因此微软并不推荐使用。
应当注意的是,由于资源文件里的所有字符串(包括类型和名称的标识字符串)都是UNICODE字符,因此用户程序中的LoadBitmap等函数都必须传入相应的UNICODE字符串作为参数(仅当程序使用UNICODE版本的API集合而不是ASCII版本的API集合时)。可以使用定义在winnt.h文件中的TEXT宏修饰字符串来自动达到此目的。
第三个目录,语言,使程序开发者可以在一个单独的可执行文件中提供多语言支持。例如,一个映像文件可以很容易地同时支持法语、加拿大法语以及比利时法语三种语言的资源。理论上一个应用程序可以同时支持UNICODE标准所支持的所有语言,虽然最终的可执行文件可能会大得难以承受。不过,由于系统提供了可以修改映像内资源的工具,因此应用程序的安装程序可以为每个用户自定义程序的映像文件,从而删掉不需要的语言以节省空间,减小最终的映像文件的大小。
2.1 新语句
Windows 32资源编译器可以处理几个新添加的语句。
2.1.1 新按钮语句
这几个语句允许程序员自由使用,就像PUSHBUTTON、DEFAULTPUSHBUTTON等语句一样。
它们的语法与PUSHBUTTON等相同。
2.1.1.1 AUTO3STATE
允许声明一个AUTO3STATE按钮。
2.1.1.2 AUTOCHECKBOX
允许声明一个AUTOCHECKBOX按钮。
2.1.1.3 AUTORADIOBUTTON
允许声明一个AUTORADIOBUTTON按钮。
2.1.1.5 PUSHBOX
允许声明一个PUSHBOX按钮。
2.1.1.6 STATE3
允许声明一个3STATE按钮(由于语法而将3放在了后面)。
2.1.1.7 USERBUTTON
允许声明一个USERBUTTON用户定义的按钮。
2.1.2 EXSTYLE语句
这个语句允许程序员指名一个对话框或控件窗口的扩展样式(WS_EX_xxx)。有三种声明方法,取决于需要什么。
它可以被直接放在DIALOG语句下面(像CAPTION和STYLE语句一样),以应用于对话框窗口。
EXSTYLE <flags>
它可以与内存标志一起被放在DIALOG语句中。
FOOBAR DIALOG [MemFlags...] [EXSTYLE=<flags>] x, y, dx, dy
它也可以被单独放在CONTROL、PUSHBUTTON、LTEXT等语句的结尾部分。
AUTOCHECHBOX "autocheckbox", id, x, y, dx, dy
[styleflags] [exstyleflags]
2.1.3 CHARACTERISTICS语句
这个语句允许程序员指定可能被(第三方)资源文件读写工具使用的资源信息。它对系统没有任何意义,而且也不会存储在映像文件中。
CHARACTERISTICS <用户定义的DWORD值>
2.1.4 VERSION语句
这个语句使程序可以在资源文件中指定资源的版本号(供读写资源文件的工具使用)。它对系统没有任何意义,而且也不会存储在映像文件中。
VERSION <用户定义的DWORD值>
2.1.5 LANGUAGE语句
LANGUAGE语句用来指定资源或资源的一段所使用的语言。它可以被放在资源脚本文件中一个单行语句(比如ICON、CURSOR、BITMAP语句)所能放置到的任何地方。它的作用域从它定义的位置开始,直到下一个LANGUAGE语句,或者文件尾。
LANGUAGE <主数字>, <次数字>
其中<主数字>表示语言ID,<次数字>表示字语言ID。应当使用在winnt.h文件中定义的值。
LANGUAGE语句也可能连同其它可选语句(如CAPTION、STYLE等)被放在MENU、DIALOG、STRINGTABLE、ACCELERATOR和RCDATA资源的BEGIN语句之前,此时它的作用域仅限于此资源。
2.1.6 MESSAGETABLE语句
MESSAGETABLE语句被用来包含一个消息表。消息表是一个有特殊意义的字符串表,通常包含一些错误消息或信息,而且可能含有格式字符串信息(如“/n”、“/t”等),它的格式是:
<nameid> MESSAGETABLE <filename>
2.1.7 UNICODE字符串的附加语法
在资源脚本中,使用双引号括起来的字符串都被看作是ASCII字符串(在当前代码页),除非使用"L"或"l"字符前缀,例如:
L"这是一个Unicode字符串"
使用这种语法定义UNICODE字符串有两个效果。在RCDATA语句中,它会使编译器将字符串存储为UNICODE而不是ASCII。在任何情况下,使用这种语法,嵌入的不可见字符将被翻译为UNICODE代码点——一个16位UNICODE字符,例如:
L"这是第一行,/x2028这是第二行"
其中0x2028 UNICODE字符是分行符(Line Separator)。任何UNICODE字符都可以被以此种方式嵌入到任何资源脚本字符串中。
3.资源头格式
整个文件的格式实际上只是一些资源文件项简单的连接到了一起。每个资源都包含一个单独的资源信息(比如一个对话框或一个字符串表)。
每个项都由一个资源头和其后的资源数据组成。一个资源头(双字对齐的)由4个元素组成:两个表示资源头和资源数据大小的双字、一个资源类型、一个资源名称,还可能有一些附加的资源信息。资源的数据跟在资源头后面,每种资源类型有其自己的数据格式。
3.1 DataSize
这个域给出了跟在资源头后面的数据的大小(不包括在此资源与后面的任何资源之间的填充数据(编者:也就是说这是此资源的实际大小))。
3.2 HeaderSize
HeaderSize域给出了资源头结构的大小。
3.3 Type
Type域要么是一个数字,要么是一个指向表示类型名称的以空值结尾的UNICODE字符串。这个可变的类型被称为“名称或序数”(Name or Ordinal)域,它经常出现在资源文件中ID出现的地方。
名称或序数域的第一个单字域标志出这个域到底是一个数字还是一个字符串。如果它等于0xffff(一个非法UNICODE字符),那么在它后面的单字信息就是一个类型序号(一个数字)。否则,这个域就是一个UNICODE字符串。
如果类型域是一个数字,那它就代表一个标准的或者用户自定义的资源类型。所有标准的Windows 资源类型都被赋予一个特定值,如下表所示。这个表摘自用来生成RC(资源编译器)的头文件,它包含了绝大多数资源类型的类型序数:
/*预定义的资源类型*/
#define RT_NEWRESOURCE 0x2000
#define RT_ERROR 0x7fff
#define RT_CURSOR 1
#define RT_BITMAP 2
#define RT_ICON 3
#define RT_MENU 4
#define RT_DIALOG 5
#define RT_STRING 6
#define RT_FONTDIR 7
#define RT_FONT 8
#define RT_ACCELERATORS 9
#define RT_RCDATA 10
#define RT_MESSAGETABLE 11
#define RT_GROUP_CURSOR 12
#define RT_GROUP_ICON 14
#define RT_VERSION 16
#define RT_NEWBITMAP (RT_BITMAP | RT_NEWRESOURCE)
#define RT_NEWMENU (RT_MENU | RT_NEWRESOURCE)
#define RT_NEWDIALOG (RT_DIALOG | RT_NEWRESOURCE)
如果类型域是一个字符串,那么这个类型是一个用户自定义的类型。
3.4 Names
名称域标识了每一个资源。根类型域一样,名称域也是一个数字或者一个字符串,区分方法也跟类型域一样。
注意:在类型域和名称域之间不需要填充(双字对齐),由于它们只包含单字数据,名称域将总是自动对齐。不过在名称域之后可能会有一个单字填充,使余下的资源头能够双字对齐。
3.5 附加头信息
附加信息包含更多的关于资源数据的信息,包括大小和语言ID。包含附加信息的资源头结构如下所示:
struct tagResource {
DWORD DataSize; //不包含头结构的资源数据的大小
DWORD HeaderSize; //附加头的长度
[类型序号或字符串] //类型标识,id或字符串
[名称序号或字符串] //名称标识,id或字符串
DWORD DataVersion; //预定义的资源数据版本
WORD MemoryFalgs; //资源状态
WORD LanguageId; //对NLS的UNICODE支持
DWORD Version; //资源数据的版本
DWORD Characteristics; //数据的特征
};
附加信息结构总是从资源文件中双字对齐的地方开始,因此在名称域与ResAdditional结构之间可能会有一些填充数据。
3.5.1 DataVersion
DataVersion域用来检查资源头里后跟的数据信息的格式。这可能会在将来用来将附加信息输入到预定义格式中去。
3.5.2 MemoryFlags
wMemoryFlags域解释一个给定资源的状态。这些属性通过在.RC脚本中添加标志来给定。脚本标志包含以下标志值:
#define MOVEABLE 0x0010
#define FIXED ~MOVEABLE
#define PURE 0x0020
#define IMPURE ~PURE
#define PRELOAD 0x0040
#define LOADONCALL ~PRELOAD
#define DISCARDABLE 0x1000
NT系统的资源编译器总是忽略MOVEABLE、IMPURE和PRELOAD标志。
3.5.3 LanguageId
语言ID包含在每个资源中,用来指定字符串的语言,当它们需要被翻译回单字节字符串时,将会需要这些信息。因此,可能会有多种资源拥有相同的类型和名称等,但仅仅语言不同。
NLS规范的附录A中有语言ID的相关文档,winnt.h中也有。资源的语言由LANGUAGE语句指定。
3.5.4 Version和Characteristics
当前的资源文件格式中也包含版本和特征域。它们分别由VERSION和CHARACTERISTICS域指定。
3.6 区分16位和32位资源文件
软件开发商可能会开发一些同时支持16位和32位资源文件的资源读写工具,微软为此提供了一个方法:使用非法的类型和名称序数值来区分。
方法是在资源文件中放置一个非法资源。微软选择了以下八个字节:
0x00 0x00 0x00 0x00 0x20 0x00 0x00 0x00
假设资源文件是16位的。此时类型域是非法的,因为第一个字节告诉我们它是一个字符串,但一个0长度的字符串是非法的。因此这是一个非法的16位资源头,也就是说这个文件是32位的。
假设资源文件是32位的。由上可知数据的大小为0,当然不可能有这种情况。
Windows 32资源编译器在每个32位资源文件中前置这几个字节的数据(后跟的是一个附加数据结构,描述一个长度为0、类型序数为0、名称序数为0的资源),用来区分16位和32位资源文件。任何想要读取资源文件的工具都应忽略这个资源。
3.7 文件对齐
有时,将资源分开到几个脚本中、分开编译这些资源文件,最后将它们合并起来会很有用,因此很有必要指出资源文件都是填充到双字大小的。如果不包括这个填充,可能会导致文件中的资源不能双字对齐。
4. 资源数据格式
对任何预定义的数据类型,所有结构都是双字对齐的,包括位图、图标、字体头结构等等。因此,数据总是从双字边界开始。
4.1 版本资源
版本资源用来记录使用资源文件的程序的版本。版本资源包含固定数量的信息。它的结构如下:
typedef struct tagVS_FIXEDFILEINFO {
DWORD dwSignature; //e.g. 0xfeef04bd
DWORD dwStrucVersion; //e.g. 0x00000042 = "0.42"
DWORD dwFileVersionMS; //e.g. 0x00030075 = "3.75"
DWORD dwFileVersionLS; //e.g. 0x00000031 = "0.31"
DWORD dwProductVersionMS; //e.g. 0x00030010 = "3.10"
DWORD dwProductVersionLS; //e.g. 0x00000031 = "0.31"
DWORD dwFileFlagsMask; // = 0x3f 对应版本 "0.42"
DWORD dwFileFlags; //e.g. VFF_DEBUG | VFF_PRERELEASE
DWORD dwFileOS; //e.g. VOS_DOS_WINDOWS16
DWORD dwFileType; //e.g. VFT_DRIVER
DWORD dwFileSubType; //e.g. VFT2_DRV_KEYBOARD
DWORD dwFileDateMS; //e.g. 0
DWORD dwFileDateLS; //e.g. 0
} VS_FIXEDFILEINFO;
4.2 图标资源
.RC脚本中的ICON语句创建的并不是一个单独的资源对象,而是一组资源。这使得Windows程序具有一定的设备独立性(根据不同的硬件配置使用不同的象素位图图标)。Windows组合一组不同象素位数和个数的位图并将它们作为一个图标资源。但在.RES和.EXE文件中,它们被以一组资源的格式存储。这些组被以组件(在此,是不同的图标[type 3])在前,后跟组头信息([Type 14])的格式存储。组头包含必要的信息以使Windows能够选择合适的图标来显示。
组件有如下的格式:
[资源头(type = 3)]
[DIB头]
[图标XOR(异或)掩码的颜色DIBits (Color DIBits of icon XOR mask)]
[AND(与)掩码的单色DIBits (Monochrome DIBits of AND mask)]
每个组件都有一个唯一的序数ID(相对于其它图标组件)。
DIB(设备无关位图)头的域分别描述了掩码的信息,仅有两项例外。第一,高度域同时描述 异或 和 与掩码。在将两个DIB转换为DDB(设备相关位图)之前,高度应当被除2。掩码的大小是固定的,占DIB头大小的一半。第二,每个象素的位数和位的数量参考异或掩码。与掩码总是单色的,只有一个面,每象素一位。在使用图标之前,请先参考一下windows sdk参考材料中关于DIB的信息。由于图标组件的格式与.ico文件非常相似,因此Windows SDK参考文档的9.2节还是很有用的。Windows 32应用程序不应使用DDB。
组头如下描述:
[组头(type = 14)]
struct IconHeader {
WORD wReserved; // 当前为0
WORD wType; // 图标在此等于1
WORD wCount; // 组件的数量
WORD padding; // 为使双字对齐的填充数据
};
下面的结构每个组件都有一个:
struct ResourceDirectory {
BYTE bWidth;
BYTE bHeight;
BYTE bColorCount;
BYTE bReserved;
WORD wPlanes;
WORD wBitCount;
DWORD lBytesInRes; // 指向组件
WORD wNameOrdinal;
WORD padding; // 填充数据
};
注意:组头由固定的头和重复的组件数据组成。这两部分都有固定的长度,以使能对组件信息进行随机访问。
组头包含了所有来自.ico头和资源描述器的数据。