1. 程式人生 > 實用技巧 >想做Windows平臺高階工程師,卻連視窗原理都不懂,朋友都勸我放棄~

想做Windows平臺高階工程師,卻連視窗原理都不懂,朋友都勸我放棄~

1、Windows程式開發流程:

Windows 程式分為「程式程式碼」和「UI資源」兩大部分,通過RC編譯器整合為一個完整的EXE 檔案。

所謂UI 資源是指功能選單、對話方塊外貌、程式圖示、游標形狀等等東西。

這些UI 資源的實際內容(二進位制程式碼)系藉助各種工具產生,並以各種副檔名存在,如.ico、.bmp、.cur 等等。程式設計師必須在一個所謂的資源描述檔(.rc)中描述它們。

RC 編譯器(RC.EXE)讀取RC 檔的描述後將所有UI資源檔集中製作出一個.RES 檔,再與程式程式碼結合在一起,這才是一個完整的Windows可執行檔案。

2、Windows程式與作業系統之間的關係

Windows 程式的進行系依靠外部發生的事件來驅動。換句話說,程式不斷等待(利用一個while 迴路),等待任何可能的輸入,然後做判斷,然後再做適當的處理。上述的「輸入」是由作業系統捕捉到之後,以訊息形式(一種資料結構)進入程式之中。

3、Windows視窗生命週期如下:

1.程式初始化過程中呼叫CreateWindow,為程式建立了一個視窗,作為程式的螢幕舞臺。CreateWindow產生視窗之後會送出 wM_CREATE直接給視窗函式,後者於是可以在此時做些初始化操作(例如配置記憶體、開啟檔案、讀初始資料……)。

2在程式活著的過程中,不斷以 GetMessage從訊息佇列中抓取訊息。如果這個訊息是WM_oUIT,GetMessage會傳回0而結束while迴圈,進而結束整個程式。

3.DispatchMessage通過Windows USER模組的協助與監督,把訊息分派至視窗函式。訊息將在該處被判別並處理。

4.程式不斷進行第2步和第3步的操作。

5.當使用者按下系統選單中的Close命令項時,系統送出WM_CLOSE。通常程式的視窗函式不攔截此訊息,於是 DefWindowProc處理它。

6.DefWindowProc收到 WM_CLOSE後,呼叫 DestroyWindow把視窗清除。Destroy Window本身又會送出WM_DESTROY。

7.程式對WM_DESTROY的標準反應是呼叫PostQuitMessage。

8.PostQuitMessage沒什麼其它操作,就只送出 WM_QUIT 訊息,準備讓訊息迴圈中的GetMessage取得,如步驟2,結束訊息迴圈。

3.Windows窗體原理

Windows的三大核心繫統:負責視窗物件產生和訊息分發的USER模組,負責影象顯示繪製的GDI模組,負責記憶體、程序、IO管理的KERNEL模組。

試想象一下如何在一個畫素陣列上產生視窗物件,其實就是使用GDI繪製視窗,不停的以一定的頻率重新整理顯示在螢幕上,這就是圖形介面,如果由在DOS或Windows DOS模擬器下編寫圖形介面的經驗這個比較好理解。所以說其實USER模組中的視窗產生是依靠GDI模組的(包括選單、滾動條等都是使用GDI來繪製的)。

那麼,下面我們就從USER模組和GDI模組來說說Windows 的窗體原理。

如果接觸過Win32 SDK程式設計的知道一個標準Windows窗體的產生過程:

  1. 設計視窗類、
  2. 註冊視窗類、
  3. 建立視窗、
  4. 顯示視窗、
  5. 啟動訊息迴圈泵迴圈獲取訊息分發到窗體過程函式處理。

貼上一個標準Windows窗體的產生程式碼:

#include <windows.h>
 
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
 
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
    static TCHAR szAppName[] = TEXT ("視窗類名稱");
    HWND         hwnd;
    MSG          msg;
    WNDCLASSEX   wndclassex = {0};
 
    //設計視窗類
    wndclassex.cbSize        = sizeof(WNDCLASSEX);
    wndclassex.style         = CS_HREDRAW | CS_VREDRAW;
    wndclassex.lpfnWndProc   = WndProc;
    wndclassex.cbClsExtra    = 0;
    wndclassex.cbWndExtra    = 0;
    wndclassex.hInstance     = hInstance;
    wndclassex.hIcon         = LoadIcon (NULL, IDI_APPLICATION);
    wndclassex.hCursor       = LoadCursor (NULL, IDC_ARROW);
    wndclassex.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH);
    wndclassex.lpszMenuName  = NULL;
    wndclassex.lpszClassName = szAppName;
    wndclassex.hIconSm       = wndclassex.hIcon;
    
    //註冊視窗類
    if (!RegisterClassEx (&wndclassex))
    {
        MessageBox (NULL, TEXT ("RegisterClassEx failed!"), szAppName, MB_ICONERROR);
        return 0;
    }
 
    //產生視窗
    hwnd = CreateWindowEx (WS_EX_OVERLAPPEDWINDOW, 
                          szAppName, 
                          TEXT ("視窗名稱"),
                          WS_OVERLAPPEDWINDOW,
                          CW_USEDEFAULT, 
                          CW_USEDEFAULT, 
                          CW_USEDEFAULT, 
                          CW_USEDEFAULT, 
                          NULL, 
                          NULL, 
                          hInstance,
                          NULL); 
            
    //顯示視窗
    ShowWindow (hwnd, iCmdShow);
    UpdateWindow (hwnd);
    
    //啟動訊息迴圈泵迴圈獲取訊息分配到窗體過程函式處理
    while (GetMessage (&msg, NULL, 0, 0))
    {
        TranslateMessage (&msg);
        DispatchMessage (&msg);
    }
 
    return msg.wParam;
}
 
//窗體過程函式
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    HDC hdc;
    PAINTSTRUCT ps;
 
    switch (message)
    {
    case WM_CREATE:
        return (0);
        
    case WM_PAINT:
        hdc = BeginPaint (hwnd, &ps);
        EndPaint (hwnd, &ps);
        return (0);
        
    case WM_DESTROY:
        PostQuitMessage (0);
        return (0);
    }
 
    return DefWindowProc (hwnd, message, wParam, lParam);
}

需要明白的是,所有Windows的窗體及控制元件歸根結底都是使用CreateWindow或CreateWindowEx來建立的,他們都需要標準Windows窗體的產生過程。

普通的窗體好理解,主要需要弄清楚是對話方塊及控制元件的產生和訊息分派處理流程。

對話方塊及其子控制元件的管理依靠Windows內建的對話方塊管理器,對話方塊管理器的工作包括:

1.根據我們在資源設計器中設計的對話方塊及子控制元件產生的.rc檔案來自動生成對話方塊和子控制元件(如果有手動編寫.rc檔案的經歷的話,知道編寫RC檔案其實就是指定視窗和子控制元件大小、型別、樣式等引數,對話方塊管理器將這些引數傳入CreateWindow函式產生窗體)

2.模態對話方塊直接顯示窗體,非模態對話方塊訊息指明WS_VISIBLE屬性的話,需要呼叫ShowWindow來顯示窗體。

3.維護一個訊息迴圈泵,對於模態對話方塊來說這個訊息泵的訊息不經過父視窗,所以表現為模態;對於非模態對話方塊這個訊息泵訊息經過主視窗,必須由主視窗傳給非模態對話方塊,表現為非模態。

4.維護一個內建的窗體過程函式,對於對話方塊來說會處理對話方塊的關閉開啟及子視窗的焦點、tab等,對於子控制元件也是一樣,每個子控制元件會有自己型別的窗體過程函式,窗體過程函式處理子控制元件的獲得或失去焦點、按下或彈起、建立等表現樣式和行為。

對於對話方塊來說,他會開放一個對話方塊過程函式,讓部分訊息先通過對話方塊管理函式處理,如果對話方塊過程函式不處理才交給預設的內建過程函式處理,對於子控制元件來說,他們並沒有開放過程函式,而是由內建窗體函式將要處理的訊息發給父視窗處理。

那麼對話方塊管理器完成了標準Windows窗體的產生中後半部分工作,至於設計視窗類和註冊視窗類這是由Windows自己預先做好了的,如常見的“button”、“listbox”、“edit”類等等。

那麼既然所有的窗體(包括對話方塊和控制元件)產生過程一樣,那麼我們就可以將對話方塊管理器的部分工作替換掉:

1.不使用對話方塊讀取.rc模板的方式,直接將引數傳遞給CreateWindow函式來建立對話方塊和控制元件,這就是常見的動態建立控制元件原理

2.設定控制元件自繪製如BS_OWNDRAW屬性,開放控制元件的WM_DRAWITEM訊息給父視窗,由父視窗來繪製按鈕樣式,這就是常見的控制元件重繪原理

3.替換內建的窗體函式,將訊息傳到自定義的窗體過程函式處理,這就是常見的控制元件子類化原理

需要Windows作業系統和開發工具的小夥伴,可以加群免費領取噢~