N多年沒有寫過 Window 程序了。爲了研究 WebRTC 源碼,這兩天從新學習一下。還記得上大學的時候看過 《Windows95 程式設計》臺灣版,對那本書印象極爲深入。一是當時國內確實沒有一本寫的那麼深刻的書籍,二是那本書翻譯的特別好,讓人一看就特別明白。10多年過多了,當時的情景還記憶猶新,也可見那本書寫的有多好了。git
Windows開發有不少知識點,窗口啊,句柄啊,消息啊,重繪啊,baba .....,但一個 Windows 程序的核心就是一個消息處理機制。github
Windows程序是消息爲驅動的,因此它的核心就是消息的傳遞與處理。如鼠標消息、鍵盤消息,Timer消息,窗口的建立與消毀等等。那麼,Windows程序是在哪兒處理消息呢?是否掌握了它,就控制了Windows程序的核心呢?答案是確定的,它就是 WndProc 函數。全部的消息都要通過這個函數處理。bash
Windows 程序有兩種消息,一種是隊列消息,它經過 DispatchMessage 函數分發給 WndProc 函數,像鼠標消息、鍵盤消息,Timer消息都是這類消息。另外一種是非隊列消息,它是系統函數直接發送給 WndProc 函數的,像窗口的建立與消毀消息,WM_COMMON消息等等都是非隊列消息。函數
一個最簡單的 Windows 程序都包括哪些內容呢?下面咱們詳細介紹一下:學習
咱們都知道不管是Windows程序,仍是Linux程序,也不管是C/C++,仍是 Java語言,它們都有一個 main 函數。更準確點說應該叫「程序入口點」。ui
咱們寫程序時,通常都以 main 開頭,編譯器在編譯該程序時,會將 main 函數地址寫入到可執行文件的文件頭中,這就是「程序入口點」了。spa
在執行程序時,操做系統首先經過程序加載器將要運行的程序加載到內存中,而後從新計算符號地址表。一切準備就緒後,才跳到程序入口點,將一條條指令送入CPU流水線開始執行程序。這就是程序的運行的基本流程。操作系統
所以,咱們能夠知道出每一個程序都有一個入口點。可是否不必定以 main 爲開頭呢? 其實,只要編譯器能識別出入口點就能夠,沒必要非要以 main 爲標誌。對於 Windows 程序就是這樣,它就不使用 main 而換做了 WinMain 做爲程序入口點。格式以下:命令行
int CALLBACK WinMain(
_In_ HINSTANCE hInstance,
_In_ HINSTANCE hPrevInstance,
_In_ LPSTR lpCmdLine,
_In_ int nCmdShow
);
複製代碼
上面一節我也介紹了 WndProc 是 Windows 程序的消息中心。全部的消息都要在這個函數中處理。如 窗口建立時發送的 WM_CREATE 消息,若是咱們不處理它,Windows 操做系統就不會顯示建立的窗口。翻譯
但 Windows 中有那麼多消息,咱們每一個都處理企不是要累死人?因此 Windows 很貼心的提供了一個API,就是 DefWindowProc 函數。在該函數中對全部的 Windows 消息都作了默認處理。若是咱們很懶的話,能夠將全部消息都交由它來處理就行了。
有沒有坐過山車的感腳?開始以爲很苦悶,忽然又撥雲見日了。嘿嘿!
LRESULT CALLBACK WndProc(
_In_ HWND hwnd,
_In_ UINT uMsg,
_In_ WPARAM wParam,
_In_ LPARAM lParam
) {
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
複製代碼
咱們在建立窗口以前要註冊一個窗口類,它是幹啥用的呢?就是告訴操做系統,我要建立個什麼樣子的窗口,是啥背景色,鼠標是啥樣子的,程序叫啥名子等等。
有了這個窗口類,咱們就能夠建立不少這樣子的窗口了,這樣是否是以爲很方便呢?固然,若是隻建立一個貌似也就沒啥子優點!
除了上面那些,它其實最最重要的做用是指定 WndProc 函數,也就是 Window 程序的 "消息中心"。消息中心是誰,徹底是由 RegisterClass 說了算。它說 WndProc 就是 WndProc,它說 WindowProc 就是 WindowProc。
它長的像下面這個樣子:
// 類名
WCHAR* cls_Name = L"My Class";
// 設計窗口類
WNDCLASS wc = { };
wc.hbrBackground = (HBRUSH)COLOR_WINDOW;
wc.lpfnWndProc = WndProc;
wc.lpszClassName = cls_Name;
wc.hInstance = hInstance;
// 註冊窗口類
RegisterClass(&wc);
複製代碼
建立窗口就比較簡單了,高多少,寬多少,透明的仍是非透明的,可顯示仍是不可顯示,標題欄上要寫啥字等等,這些都是它說了算。形式以下:
// 建立窗口
HWND hwnd = CreateWindow(
cls_Name, //類名,要和剛纔註冊的一致
L"個人應用程序", //窗口標題文字
WS_OVERLAPPEDWINDOW, //窗口外觀樣式
38, //窗口相對於父級的X座標
20, //窗口相對於父級的Y座標
480, //窗口的寬度
250, //窗口的高度
NULL, //沒有父窗口,爲NULL
NULL, //沒有菜單,爲NULL
hInstance, //當前應用程序的實例句柄
NULL); //沒有附加數據,爲NULL
複製代碼
窗口建立完了,還要主動調函數讓它顯示出來,不然它是不會出來幹活的。形式以下:
// 顯示窗口
ShowWindow(hwnd, SW_SHOW);
複製代碼
這部分工做是在 WinMain 函數中要作的事兒。在 WinMain 中寫一個循環,不停的從系統消息隊列中取消息。
若是此時沒有消息,則該線被程阻塞,並將CPU資源釋放;若是有消息,須要判斷是否是退出消息?若是不是,使用 DispatchMessage 將該消息分配出去。若是是退出消息,則退出消息循環,程序結束。代碼以下:
void WinMan(...){
...
// 消息循環
MSG msg;
while(GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
複製代碼
以上就是一個最簡單的窗口 Window 程序。瞭解了上在的知識,你們是否是以爲不用 MFC 本身寫個 Windows 程序也不是很難了?
int CALLBACK WinMain(
_In_ HINSTANCE hInstance, //句柄
_In_ HINSTANCE hPrevInstance, //老是 NULL
_In_ LPSTR lpCmdLine, //在命令行啓動程序時的命令
_In_ int nCmdShow //程序啓動時的顯示方式
);
複製代碼
typedef struct tagWNDCLASS {
UINT style;
WNDPROC lpfnWndProc;
int cbClsExtra;
int cbWndExtra;
HINSTANCE hInstance;
HICON hIcon;
HCURSOR hCursor;
HBRUSH hbrBackground;
LPCTSTR lpszMenuName;
LPCTSTR lpszClassName;
} WNDCLASS, *PWNDCLASS;
ATOM WINAPI RegisterClass(
_In_ const WNDCLASS *lpWndClass
);
複製代碼
HWND WINAPI CreateWindow(
_In_opt_ LPCTSTR lpClassName,
_In_opt_ LPCTSTR lpWindowName,
_In_ DWORD dwStyle,
_In_ int x,
_In_ int y,
_In_ int nWidth,
_In_ int nHeight,
_In_opt_ HWND hWndParent,
_In_opt_ HMENU hMenu,
_In_opt_ HINSTANCE hInstance,
_In_opt_ LPVOID lpParam
);
複製代碼
本文首先介紹了一個Windows程序程序是由消息驅動的,它的核心是註冊窗口類API RegisterClass 中指定的 WinProc 函數。WinProc是Windows消息處理中心,全部的消息都要交由它來處理。而後對一個最簡單的 Windows程序作了剖析,指出經過 6 大步能夠建立出一個最簡單的 Windows程序,它們分別是:
至此,一個Windows程序窗口已經展示在你面前了。後面就能夠往裏不斷的增長內容了。
但願本文能對你有所幫助,謝謝!github地址