爲了分析WebRTC, 重學Windows開發


N多年沒有寫過 Window 程序了。爲了研究 WebRTC 源碼,這兩天從新學習一下。還記得上大學的時候看過 《Windows95 程式設計》臺灣版,對那本書印象極爲深入。一是當時國內確實沒有一本寫的那麼深刻的書籍,二是那本書翻譯的特別好,讓人一看就特別明白。10多年過多了,當時的情景還記憶猶新,也可見那本書寫的有多好了。git

Windows開發有不少知識點,窗口啊,句柄啊,消息啊,重繪啊,baba .....,但一個 Windows 程序的核心就是一個消息處理機制。github

Windows程序運行的基本原理

Windows程序是消息爲驅動的,因此它的核心就是消息的傳遞與處理。如鼠標消息、鍵盤消息,Timer消息,窗口的建立與消毀等等。那麼,Windows程序是在哪兒處理消息呢?是否掌握了它,就控制了Windows程序的核心呢?答案是確定的,它就是 WndProc 函數。全部的消息都要通過這個函數處理。bash

Windows 程序有兩種消息,一種是隊列消息,它經過 DispatchMessage 函數分發給 WndProc 函數,像鼠標消息、鍵盤消息,Timer消息都是這類消息。另外一種是非隊列消息,它是系統函數直接發送給 WndProc 函數的,像窗口的建立與消毀消息,WM_COMMON消息等等都是非隊列消息。函數

最簡單的 Windows 程序

一個最簡單的 Windows 程序都包括哪些內容呢?下面咱們詳細介紹一下:學習

WinMain 函數

咱們都知道不管是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()

上面一節我也介紹了 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 程序也不是很難了?

重要函數詳細介紹

WinMain

int CALLBACK WinMain(
  _In_ HINSTANCE hInstance, //句柄
  _In_ HINSTANCE hPrevInstance, //老是 NULL
  _In_ LPSTR     lpCmdLine, //在命令行啓動程序時的命令
  _In_ int       nCmdShow //程序啓動時的顯示方式
);
複製代碼
  • hInstance:句柄,就是一個內存地址,在該地址上有該程序的基本信息。
  • hPrevInstance:老是NULL,沒啥用。
  • lpCmdLine: 用命令行啓動時的命令,有興趣的能夠本身打印出來。
  • 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
);
複製代碼
  • style :設置窗口樣式。能夠不設置。
  • lpfnWndProc :這個字段特別重要,設置消息處理函數,它是消息的中心。
  • cbClsExtra :不用設置。
  • cbWndExtra :不用設置。
  • hInstance :窗口句柄,與WinMain中的同樣。
  • hIcon :窗口圖標。若是是NULL,使用默認圖標。
  • hCursor :設置光標樣式。能夠不設置
  • hbrBackground :設置窗口背景色。
  • lpszMenuName:菜單名。若是爲NULL說明沒有菜單。
  • lpszClassName:這個參數要提供,長度不超過 256。

建立窗口

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
);
複製代碼
  • lpClassName : 與註冊的類名子一致。
  • lpWindowName :窗口標題欄名子。
  • dwStyle :窗口外觀樣式。
  • x :窗口起始位置 x。
  • y :窗口起始位置 y。
  • nWidth :窗口寬度。
  • nHeight :窗口高度。
  • hWndParent :父窗口,沒有的話設置爲NULL
  • hMenu :窗口菜單,沒有設置爲NULL
  • hInstance : 窗口句柄。
  • lpParam :符加數據,沒有設置爲 NULL

小結

本文首先介紹了一個Windows程序程序是由消息驅動的,它的核心是註冊窗口類API RegisterClass 中指定的 WinProc 函數。WinProc是Windows消息處理中心,全部的消息都要交由它來處理。而後對一個最簡單的 Windows程序作了剖析,指出經過 6 大步能夠建立出一個最簡單的 Windows程序,它們分別是:

  • 設置入口點,WinMain。
  • 建立 WinProc 函數。
  • 註冊窗口類。
  • 建立窗口。
  • 顯示窗口。
  • 循環處理,檢索與分發消息

至此,一個Windows程序窗口已經展示在你面前了。後面就能夠往裏不斷的增長內容了。

但願本文能對你有所幫助,謝謝!github地址

相關文章
相關標籤/搜索