WINDOWS32資源格式(1)

文章出處: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頭和資源描述器的數據。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章