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