Windows 程式設計/控制代碼和資料型別
許多 Win32 API 的初學者都會發現,要處理的舊資料型別數量眾多。有時,僅僅記住所有正確的資料型別順序比編寫一個好的程式更難。本頁面將介紹一些程式設計師會遇到的資料型別。
首先,讓我們快速瞭解一下某些資料型別和某些變數的命名約定。Win32 API 使用所謂的“匈牙利命名法”來命名變數。匈牙利命名法要求變數以其資料型別縮寫作為字首,這樣您在閱讀程式碼時就可以確切地知道它是哪種型別的變數。Win32 API 實施這種做法的原因是資料型別眾多,難以全部記住。此外,有許多本質上定義相同的資料型別,因此某些編譯器在錯誤使用它們時不會發現錯誤。在討論每個資料型別時,我們也會說明該資料型別的常用字首。
在資料型別或變數前加上字母“P”或“p”通常表示該變數是指標。字母“LP”或字首“lp”代表“長指標”,在 32 位機器上與普通指標完全相同。LP 資料物件僅僅是從 Windows 3.1 或更早版本繼承下來的舊物件,當時指標和長指標需要區分。在現代的 32 位系統上,這些字首可以互換使用。
LPVOID 資料型別被定義為“指向空物件指標”。這對某些人來說可能很奇怪,但是 ANSI-C 標準允許將通用指標定義為“void*”型別。這意味著 LPVOID 指標可以用於指向任何型別的物件,而不會產生編譯器錯誤。但是,程式設計師有責任跟蹤指向的是哪種型別的物件。
此外,某些 Win32 API 函式可能包含標記為“LPVOID lpReserved”的引數。這些保留的資料成員永遠不要在程式中使用,因為它們要麼依賴於尚未由 Microsoft 實現的功能,要麼只在某些應用程式中使用。如果您看到一個帶有“LPVOID lpReserved”引數的函式,則必須始終為該引數傳遞 NULL 值 - 如果您沒有這樣做,某些函式將會失敗。
LPVOID 物件通常沒有字首,儘管在 LPVOID 變數前加字母“p”作為字首比較常見,因為它是一個指標。
這些資料型別被定義為特定長度,與目標平臺無關。標頭檔案中有相當多的額外複雜性才能實現這一點,但結果是程式碼非常標準化,並且可以很好地移植到不同的硬體平臺和不同的編譯器。
DWORD(雙字),這些資料型別中最常見的,被定義為始終為無符號 32 位量。在任何機器上,無論是 16 位、32 位還是 64 位,DWORD 始終是 32 位長。由於這種嚴格的定義,DWORD 在 32 位機器上非常普遍且流行,但在 16 位和 64 位機器上不太常見。
WORD(單字)被嚴格定義為無符號 16 位值,與您正在程式設計的機器無關。BYTE 被嚴格定義為無符號 8 位值。QWORD(四字),雖然很少見,但被定義為無符號 64 位量。在這些識別符號的前面加上“P”表示該變數是指標。在前面加上兩個“P”表示它是指向指標的指標。這些變數可能沒有字首,或者它們可以使用 DWORD 的任何常用字首。由於編譯器的差異,這些資料型別的定義可能不同,但通常使用這些定義。
#include <stdint.h>
typedef uint8_t BYTE; typedef uint16_t WORD; typedef uint32_t DWORD; typedef uint64_t QWORD;
請注意,這些定義在所有編譯器中都不相同。眾所周知,GNU GCC 編譯器使用長和短說明符的方式與 Microsoft C 編譯器不同。因此,Windows 標頭檔案通常會根據所使用的編譯器對這些資料型別使用條件宣告。這樣可以使程式碼更具可移植性。
像往常一樣,我們可以將這些型別的指標定義為
#include <stdint.h>
typedef uint8_t * PBYTE; typedef uint16_t * PWORD; typedef uint32_t * PDWORD; typedef uint64_t * PQWORD;
typedef uint8_t ** PPBYTE; typedef uint16_t ** PPWORD; typedef uint32_t ** PPDWORD; typedef uint64_t ** PPQWORD;
DWORD 變數通常以“dw”為字首。同樣,我們有以下字首
| 資料型別 | 字首 |
|---|---|
| BYTE | "b" |
| WORD | "w" |
| DWORD | "dw" |
| QWORD | "qw" |
這些型別沒有定義為特定長度。主機確定每個型別到底有多少位。
- 型別
typedef long LONG; typedef unsigned long ULONG; typedef int INT; typedef unsigned int UINT; typedef short SHORT; typedef unsigned short USHORT; typedef char CHAR; typedef unsigned char UCHAR;
- LONG 表示法
- LONG 變數通常以“l”(小寫 L)為字首。
- UINT 表示法
- UINT 變數通常以“i”或“ui”為字首,表示它是整數並且是無符號的。
- CHAR、UCHAR 表示法
- 這些變數通常分別以“c”或“uc”為字首。
如果變數的大小無關緊要,則可以使用這些整數型別。但是,如果您想精確指定變數的大小,使其具有特定數量的位,請使用 BYTE、WORD、DWORD 或 QWORD 識別符號,因為它們的長度與平臺無關且永不改變。
STR 資料型別是字串資料型別,已分配儲存空間。這種資料型別不如 LPSTR 常用。STR 資料型別用於將字串視為立即陣列,而不是簡單字元指標。STR 資料型別的變數名字首是“sz”,因為它是一個以零結尾的字串(以空字元結尾)。
大多數程式設計師不會將變數定義為 STR,而是選擇將其定義為字元陣列,因為將其定義為陣列允許顯式設定陣列的大小。此外,在堆疊上建立大型字串會導致非常不希望出現的堆疊溢位問題。
LPSTR 代表“指向 STR 的長指標”,本質上是定義為這樣的
#define STR * LPSTR;
LPSTR 可以像其他字串物件一樣使用,只是 LPSTR 被明確定義為 ASCII,而不是 Unicode,並且此定義將在所有平臺上生效。LPSTR 變數通常以字母“lpsz”為字首,表示“指向以零結尾的字串的長指標”。字首的“sz”部分很重要,因為 Windows 中的一些字串(尤其是在談論 DDK 時)不是以零結尾的。LPSTR 資料型別和以“lpsz”為字首的變數都可以與標準庫 <string.h> 函式無縫使用。
TCHAR 資料型別,如 Unicode 部分所述,是通用字元資料型別。TCHAR 可以儲存標準的 1 位元組 ASCII 字元或寬的 2 位元組 Unicode 字元。由於此資料型別是由宏定義的,並且不是固定的,因此只應將字元資料與該型別一起使用。TCHAR 的定義類似於以下內容(儘管對於不同的編譯器可能有所不同)
#ifdef UNICODE #define TCHAR WORD #else #define TCHAR BYTE #endif
TCHAR 的字串通常稱為 TSTR 資料型別。更常見的是,它們被定義為 LPTSTR 型別,如下所示
#define TCHAR * LPTSTR
這些字串可以是 UNICODE 或 ASCII,具體取決於 UNICODE 宏的狀態。LPTSTR 資料型別是指向通用字串的長指標,可以包含 ASCII 字串或 Unicode 字串,具體取決於所使用的環境。LPTSTR 資料型別也以字母“lpsz”為字首。
HANDLE 資料型別是 Win32 程式設計中最重要的一些資料物件,也是新程式設計師最難理解的一些資料物件。在核心內部,Windows 維護著一個包含核心負責的所有不同物件的表格。Windows、按鈕、圖示、滑鼠指標、選單等等,都在表格中有一個條目,每個條目都被分配了一個唯一的地址,稱為 HANDLE。如果你想從表格中選取一個特定的條目,你需要給 Windows 提供 HANDLE 值,Windows 會返回相應的表格條目。
HANDLE 被定義為 void 指標 (void*)。它們用作程式中每個 Windows 物件(如按鈕、視窗、圖示等)的唯一識別符號。具體來說,它們的定義如下:typedef PVOID HANDLE; 和 typedef void *PVOID; 換句話說,HANDLE = void*。
HANDLE 通常以 "h" 為字首。控制代碼是 Windows 在內部用來跟蹤記憶體中物件的無符號整數。Windows 會移動記憶體中的物件,例如記憶體塊,以騰出空間,如果物件在記憶體中被移動,控制代碼表就會被更新。
以下是一些值得討論的特殊控制代碼
HWND
[edit | edit source]HWND 資料型別是 "指向視窗的控制代碼",用於跟蹤出現在螢幕上的各種物件。要與特定視窗通訊,你需要擁有視窗控制代碼的副本。HWND 變數通常以字母 "hwnd" 為字首,這樣程式設計師就知道它們很重要。
通常,主視窗被定義為
HWND hwnd;
子視窗被定義為
HWND hwndChild1, hwndChild2...
而對話方塊控制代碼被定義為
HWND hDlg;
雖然你可以自由地在自己的程式中為這些變數命名任何你想要的名字,但是當選擇了一個特立獨行的命名方案(或者更糟的是,根本沒有方案)時,可讀性和相容性就會受到影響。
HINSTANCE
[edit | edit source]HINSTANCE 變數是指向程式例項的控制代碼。每個程式都獲得一個例項變數,這很重要,這樣核心才能與程式通訊。例如,如果你想建立一個新視窗,你需要將你的程式的 HINSTANCE 變數傳遞給核心,這樣核心就知道新視窗屬於哪個程式例項。如果你想與另一個程式通訊,擁有該程式例項控制代碼的副本通常非常有用。HINSTANCE 變數通常以 "h" 為字首,而且由於一個程式中通常只有一個 HINSTANCE 變數,因此將該變數宣告為這樣的規範是
HINSTANCE hInstance;
將此 HINSTANCE 變數設為全域性值通常是有益的,這樣你的所有函式在需要時都可以訪問它。
HMENU
[edit | edit source]如果你的程式有一個下拉選單可用(大多數視覺化 Windows 程式都有),那麼該選單將有一個與之關聯的 HMENU 控制代碼。要顯示選單或更改其內容,你需要訪問此 HMENU 控制代碼。HMENU 控制代碼通常以 "h" 為字首。
WPARAM, LPARAM
[edit | edit source]在 Microsoft Windows 的早期,引數以兩種格式之一傳遞給視窗:WORD 長度 (16 位) 引數和 LONG 長度 (32 位) 引數。這些引數型別被定義為 WPARAM (16 位) 和 LPARAM (32 位)。但是,在現代 32 位和 64 位系統中,WPARAM 和 LPARAM 都能容納一個指標,因此分別為 32 位和 64 位長。但是,由於歷史原因,名稱沒有改變。
WPARAM 和 LPARAM 變數是通用的函式引數,經常被型別轉換為其他資料型別,包括指標和 DWORD。