Windows程序設計是一種面向對象的編程。Windows操作系統以數據結構的形式定義了大量預定義的對象作為操作系統的數據類型。Windows動態鏈接庫提供了各種各樣的API接口函數供Windows應用程序調用。一個Windows應用程序是運行在Windows操作系統之上的。這些API接口函數的調用所實現的功能僅僅是冰山的一角,更多的功能實現都是Windows操作系統內核自動實現的。我們對此一定要有清醒的認識。
本節必須掌握的知識點:
??? ????Windows數據類型
??? ????匈牙利命名法
??? ????窗口
??? ????消息
2.1.1 Winodws數據類型
?????? Windows操作系統有自己的數據類型,且往往以對象名的方式定義數據類型的名稱。
Windows 支持的數據類型用于定義函數返回值、函數和消息參數以及結構成員。 它們定義這些元素的大小和含義。 有關Windows數據類型的信息可以查閱MSDN,搜索“Windows數據類型”。由于Windows操作系統可以運行各種編程語言的應用程序,因此,也支持匯編語言、C\C++語言的數據類型。當我們調用Windows API函數時自然使用的是Windows定義的數據類型。
下表包含以下數據類型:字符、整型、布爾、指針和句柄。 字符、整數和布爾類型是大多數 C 編譯器通用的。 大多數指針類型名稱以P或LP前綴開頭。句柄引用已加載到內存中的資源。除此之外,我們還會陸續接觸大量Windows預定義的對象結構類型。
Windows數據類型參見《附錄A》。
提示
?????? 請讀者不要死記硬背任何內容,否則就失去了學習的興趣,Windows程序設計將變得枯燥無味。只要可以查閱到的知識都等同于已經記住了。學習的過程關鍵在于理解、動手實驗和勤于思考。為了加深印象,可以認真做好筆記,方便查閱,提高學習效率,僅此而已。
2.1.2 匈牙利命名法
在程序中需要給一些常量值、自定義數據類型、變量、函數命名,隨著代碼量的增大,對于初學者而言這似乎是一件比較麻煩的事情。其實我們只需要遵循既定的一些命名規范就可以了。
■四種基本的編程命名規范
匈牙利命名法、駝峰式命名法、帕斯卡命名法和下劃線命名法。
●匈牙利命名法
匈牙利命名法是由微軟的一個匈牙利人發明的,Windows操作系統遵循的就是匈牙利命名法。該命名規范,要求前綴字母用變量類型的縮寫,其余部分用變量的英文或英文的縮寫,單詞第一個字母大寫。匈牙利標記法可以避免因數據類型不匹配造成的錯誤。
1.變量類型:
Ex
int iMyAge;??????? ??? #? "i": int
char cMyName[10];? ? #? "c": char
float fManHeight;? ???? #? "f": float
其他前綴類型還有:
a??????????? 數組(Array)
b????? ? 布爾值(Boolean)
by???? ? 字節(Byte)
c????? ? 有符號字符(Char)
cb???? ? 無符號字符(Char Byte)
cr???? ?? 顏色參考值(Color Ref)
cx,cy? ??? 坐標差(長度 Short Int)
dw???? 雙字(Double Word)
fn???? ? 函數(Function)
h????? ? Handle(句柄)
i????? ?? 整形(Int)
l????? ?? 長整型(Long Int)
lp???? ?? 長指針(Long Pointer)
m_???? 類成員(Class Member)
n????? ? 短整型(Short Int)
np???? 近程指針(Near Pointer)
p????? ? 指針(Pointer)
s????? ? 字符串(String)
sz???? ? 以 Null 做結尾的字符串型(String with Zero End)
w????? 字(Word)
舉例
szCmdLine的前綴sz表示“以零結尾的字符串”。
hInstance和hPrevInstance中的前綴h表示“句柄”;
iCmdShow中的前綴i表示“整型”。
2.結構類型
當命名結構變量時,可以使用結構名(或結構名稱的縮寫)的小寫形式作為變量名稱的前綴或整個變量名。
舉例
HELLOWIN.C的WinMain函數中,msg變量就是一個MSG類型的結構,wnclass是一個WNDCLASS類型的結構。
在WndProc函數中,ps是一個PAINTSTRUCT結構,而rect是一個RECT結構。
3.常量類型
Windows中的常量使用大寫標識符。這些標識符都在windows頭文件中定義的。這些標識符有很多都是以兩個或三個字母作為前綴,且其后綴緊跟一個下劃線。
舉例
CS_HREARAW?????? DT_VCENTER??????? SND_FILENAME??? ?????? CS_VREDRAW??????
IDC_ARROW???????? WM_CREATE???????? CW_USEDEFAULT ?????? IDI_APPLICATION? WM_DESTROY????? DT_CENTER?? ?????? MB_ICONERROR????????? WM_PAINT?????????? DT_SINGLELINE???? SND_ASYNC???????? WS_OVERLAPPEDWINDOW
前綴 | 常量 | 含義 |
CS | 類風格選項 | CLASS |
CW | 創建窗口選項 | CreateWindow |
DT | 文本繪制選項 | DrawText |
IDI | 圖標的ID號 | Icon ID |
IDC | 光標的ID號 | Cursor ID |
MB | 消息框選項 | MessageBox |
SND | 聲音選項 | Sound |
WM | 窗口消息 | Windows Message |
WS | 窗口風格 | Windows style |
●駝峰式命名法
駝峰式命名法,又叫小駝峰式命名法。該命名規范,要求第一個單詞首字母小寫,后面其他單詞首字母大寫,簡單粗暴易學易用。
舉例
int myAge;
char myName[10];
float manHeight;
●帕斯卡命名法
帕斯卡命名法,又叫大駝峰式命名法。與小駝峰式命名法的最大區別在于,每個單詞的第一個字母都要大寫。
舉例
int MyAge;
char MyName[10];
float ManHeight;
●下劃線命名法
下劃線命名法要求單詞與單詞之間通過下劃線連接。尤其在宏定義和常量中使用比較多,通過下劃線來分割全部都是大寫的單詞。
舉例
int my_age;
char my_name[10];
float man_height;
隨著技術的發展,命名規范也在不斷的細化。不同的語言不同 IDE 推崇的規范也有所不同,無法評判哪一種最好,可以根據個人習慣或者指定的規范選擇合適的命名規則。
例如,谷歌 C++ 編程規范,從項目的命名到文件的命名,再到類和變量以及宏定義的命名都做到了細致入微,充分的結合了下劃線命名法與駝峰式命名法。
當然,命名規范并不代表著編程規范,僅僅是編程規范的一部分而已,除去命名規范,還有很多編程上的細節是必須關注的,例如,等號兩邊留空格還是等號對齊?空行什么時候什么地方留更加符合代碼結構?空格什么時候什么地方留更加美觀?花括號是否對齊?諸如此類,還有很多,無法一下子全部掌握并應用,但是在編程經驗增加的過程中,一定也要不斷的留意,自己所在的公司部門使用的是什么樣的規范,并不提倡大家練就自己的規范,一定要去融入工作環境的需求。每次去新的工作環境,第一個要看的文檔一定是編程規范。
2.1.3 窗口
Windows窗口是用來和用戶進行交互信息的,可以在窗口中輸入信息,也可以在窗口中輸出信息。這些信息包括文本、圖形、動畫等等。對比控制臺黑窗口,Windows窗口可以輸入和輸出的信息要豐富的多。
■自定義窗口
窗口就是對象,作為程序員來說,就是一塊內存。這塊內存中通常包含一個標題欄、一個菜單欄、一個工具欄和一個滾動條等其他小的對象(更小的一塊內存)。通常我們會使用窗口對象的句柄對窗口進行操作。
如果我們要創建一個窗口,需要告訴Windows系統一些明確的信息:
typedef struct tagWNDCLASSA {
? UINT????? ?????? style;???????????????????? //創建一個什么樣風格的窗口
? WNDPROC?? ? lpfnWndProc;??????? //由誰來負責處理窗口的消息—窗口過程
? int?????? ???????? cbClsExtra; ????????? //要根據窗口類結構分配的額外字節數
? int?????? ???????? cbWndExtra; ?????? //在窗口實例之后分配的額外字節數
? HINSTANCE ???? hInstance; ?????????? //窗口所屬的進程
? HICON???? ???? hIcon; ???????????????? //窗口的圖標
? HCURSOR?? ??? hCursor;?????????????? //窗口的鼠標
? HBRUSH??? ???? hbrBackground;??? //窗口的背景顏色
? LPCSTR??? ????? lpszMenuName;??? //窗口所包含的菜單
? LPCSTR??? ????? lpszClassName;???? //窗口類名(等同于進程名)
} WNDCLASSA, *PWNDCLASSA, *NPWNDCLASSA, *LPWNDCLASSA;
顯然,將創建窗口的所有信息組合到一起就是一個結構體。細心的讀者可能會發現,創建窗口的信息還應該包含窗口的尺寸。這是一定需要的,我們將在下一節中使用示例代碼來說明。
■預定義對話框窗口
Windows還預定義另一種窗口,可以不帶標題欄,而是包含各種各樣的控件,例如按鈕、列表框、滾動條和文本框等。
對話框窗口可以分為:
●模態對話框:當程序顯示一個模態對話框時,用戶不能在對話框和該程序的其他窗口之間進行切換。用戶必須先明確地終止該對話框。這通常由單擊OK或Cancel按鈕來實現。但是當對話框正在顯示時,用戶可以切換到其他的程序。有些對話框(所謂“系統模態”)則連這種切換都不允許。在Windows 中,用戶必須先結束系統模態對話框才可以進行其他操作。典型的默認對話框就是MessageBox框了。
●非模態對話框:非模態對話框允許用戶在對話框和窗口之間,以及在對話框和其他程序之間進行切換。非模態對話框因此更接近于程序自定義的正常彈出窗口。如果在一段時間內,一直要顯示某個對話框,用戶更傾向于使用非模態對話框,因為它更方便。典型的非模態對話框如查找和替換對話框。
●公用對話框:為了方便用戶快速熟悉和使用Windows應用程序,從Windows 3.1開始,提供了統一標準的“公用對話框庫”(common dialogue box library)。這個庫包括了一些函數,可以用來激活打開和存儲文件、查找和替換、選擇顏色、選擇字體(所有這些將在本書第十一章中演示)以及打印(將在第十三章中演示)的對話框。
■預定義子窗口控件
子窗口控件也是一種窗口,也是Windows預定義的窗口類型。雖然我們也可以自定義子窗口控件,但是直接使用Windows提供的標準子窗口控件更為方便快捷。Windows預定義了子窗口控件的窗口類和窗口過程,并且提供多種不同風格的子窗口樣試供我們選擇。常用的子窗口控件包括按鈕類的BUTTON控件、靜態文本控件、滾動條控件、文本編輯控件和列表框控件。我們將在第八章詳細講解子窗口控件。
2.1.4 消息
?????? Windows操作系統是一種消息驅動的系統,這句話應該被每一位從事Windows程序設計的開發人員所熟知。那么什么是消息呢?也許有很多已經從事多年Windows程序開發的程序員都無法說清楚。這是因為他們并不清楚消息傳遞的實現過程。
■消息的本質
?????? 消息其實非常簡單,就是函數調用時傳遞的參數,僅此而已。只不過我們并不能從代碼中真實的感受到消息傳遞的整個過程,所以會覺得不可琢磨。Windows操作系統中有很多很多消息,這些消息可以是鍵盤消息、鼠標消息、滾動條消息、字符消息、繪圖消息、控件消息、菜單消息或者是系統消息等等。鍵盤消息和鼠標消息肯定是由用戶操作鍵盤和鼠標產生的,這是人機交互的需要。其他消息又是如何產生的呢?如果我們把消息看作是參數,那么這些消息肯定是在調用函數調用傳遞參數時產生的,消息本身就是參數。例如繪圖時,我們將繪圖消息WM_PAINT作為實參傳遞給GDI繪圖函數。不論什么樣的消息都是由操作系統捕捉并分發的。API函數的調用和執行也是由Windows操作系統實現的,這些都是潛藏在水面以下的冰山。正是因為我們看不見,所以才會感到困惑。
?????? 既然消息就是一個參數,我們來了解一下消息本身:
typedef struct tagMSG {
? HWND?? hwnd;?????? //接收消息的窗口的句柄
? UINT?? message;???? //指定消息類型-消息ID,例如 WM_PAINT、WM_CLOSE等。
? WPARAM wParam;?? //針對特定消息的附加信息
? LPARAM lParam;????? //詳細的附加信息,通常與wParam一起配合使用
? DWORD? time; ?????? //消息發布的時間
? POINT? pt;?????????????? //鼠標在發布消息的時候在屏幕上的位置(鼠標消息特性)
? DWORD? lPrivate;??? //僅在某些消息類型中使用,其他情況下是保留項
} MSG, *PMSG, *NPMSG, *LPMSG;
?????? 我們用一個結構體來描述消息,顯然消息是一個對象(Windows系統處處是對象)。消息結構體中包含7個成員,我們只需要關系前面四個成員,后面三個成員僅提供給操作系統使用。
?????? hwnd:接收消息的窗口的句柄,所有的消息都是發送給指定窗口的。
message:指定消息類型-消息ID,Windows系統使用一個32位無符號整數標識消息。
wParam 和lParam:針對特定消息的附加信息。僅有一個消息ID遠遠不夠的,還需要借助于32位的wParam 和32位的lParam傳遞具體的信息。
我們在Windows程序中通常會將上述四個成員作為參數傳遞。
舉例
例如發送一個消息:
LRESULT SendMessage(
? [in] HWND?? hWnd,?????? //接收消息的窗口句柄
? [in] UINT?? Msg,???????????? //消息ID
? [in] WPARAM wParam,??? //附加參數,其他的消息特定信息
? [in] LPARAM lParam??????? //附加參數,其他的消息特定信息
);
?????? 很顯然,這里的消息就是參數。在Windows系統內部產生和傳遞消息也是如此。
?????? Windows系統中的消息雖然非常多,但是絕大多數消息都是由Windows系統按照默認方式處理的,不需要我們勞心費力,目的當然是不想為難我們這些開發者了。
?????? ■窗口過程
在Winodws程序中,我們需要寫一個函數來處理各種消息,這個函數稱為窗口過程。它是一個回調函數。正常情況下,在Windows程序中是由我們來調用操作系統的API函數,回調的意思是由操作系統反過來調用我們寫的窗口過程來處理消息。窗口過程只需要處理我們關心的一小部分消息,絕大多數消息都是交給默認的Windows系統的窗口過程來處理。本書的學習過程也就是學習各種各樣消息處理的過程。
■隊列消息
?????? Windows操作系統的消息可以分為隊列消息和非隊列消息。Windows操作系統中有一個總消息隊列,所有進程的隊列消息都會被送入總消息隊列中。然后再根據消息所屬的窗口,將消息分發到各個窗口隊列。還記得消息結構的第一個成員就是接收消息的窗口。以此判斷該消息屬于哪個窗口。
什么樣的消息屬于隊列消息呢?鼠標消息、鍵盤消息肯定是屬于隊列消息,一定會被送入消息隊列。這是由于用戶通過鼠標、鍵盤與窗口進行交互產生的消息,而用戶的操作是有先后順序的,因此需要使用隊列進行同步操作。因此可以認為,凡是與窗口進行交互或需要保持同步操作的消息都是隊列消息,一定會被送上消息隊列。隊列消息都是調用PostMessage函數將消息送入指定窗口的消息隊列。
BOOL PostMessage( HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam);
?????? 消息隊列存在于線程中,理論上每個線程都可以創建一個消息隊列。
假如一個Windows進程只有一個主線程,那么非常簡單,所有的窗口消息都是送入主線程的消息隊列。
假如一個Windows進程同時擁有多個線程,其中只有一個主線程負責處理窗口消息,其它線程執行其他任務。意思是在多線程的Windows程序中,同樣只有一個消息隊列,所有消息都交給主線程處理,不存在多個線程間的沖突問題。因此,雖然窗口是不包含消息隊列的,但是我們將線程消息隊列稱為窗口消息隊列也沒什么錯。
在Windows程序設計中,需要遵循“十分之一秒原則”。“十分之一秒原則”是指用戶在做出操作后,至少應在0.1秒之內得到某種形式的反饋,這樣才能讓用戶感覺到系統在對他的操作給予響應。如果一個任務執行時間超過0.1秒,我們為了良好的用戶體驗,就需要考慮新建一個線程來單獨處理這個任務。我們將在本書的第二十章詳細講解進程與線程。
windows應用程序中一般都包含一小段稱為“消息循環”的代碼,該段代碼用于從消息隊列中檢索消息,并將其分發給相應的窗口過程。其他非隊列消息則不經過消息隊列直接發送給窗口過程。
■非隊列消息
?????? 除了隊列消息之外,剩下的消息就都屬于非隊列消息了。不需要被送入消息隊列的消息可以通過SendMessage函數將消息直接發送給負責處理消息的窗口過程。
在Windows程序設計中,SendMessage函數被用于將一個消息發送給指定窗口或線程,它會等待消息處理完畢才會返回,也就是說它是一個同步或者說阻塞的函數。這是一個非常重要的用來在窗口間或線程間通信的函數。SendMessage函數可以在同一個線程內、同一個進程內的線程間或者不同進程的線程間通過消息進行通信。
下面是SendMessage函數的定義:
LRESULT SendMessage( HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam);
?????? SendMessage函數應該是使用最頻繁的一個函數了,在后面的章節中我們將深有體會。