VC++深入詳解--之複習筆記(二)

Mobidogs發表於2020-04-04

 4.  WinMain函式的定義

WinMainWindows程式的入口點函式,與DOS程式的入口點函式main的作用相同,當WinMain函式結束或返回時,Windows應用程式結束。
WinMain函式的原型宣告如下:
int WINAPI WinMain(
    HINSTANCE hInstance,         // handle to current instance
    HINSTANCE hPrevInstance, // handle to previous instance
    LPSTR lpCmdLine,              // command line
    int nCmdShow                  // show state
);
WinMain函式接收4個引數,這些引數都是在系統呼叫WinMain函式時,傳遞給應用程式的。
第一個引數hInstance表示該程式當前執行的例項的控制程式碼,這是一個數值。
當程式在Windows下執行時,它唯一標識執行中的例項(注意,只有執行中的程式例項,才有例項控制程式碼)。一個應用程式可以執行多個例項,每執行一個例項,系統都會給該例項分配一個控制程式碼值,並通過hInstance引數傳遞給WinMain函式。
 
第二個引數hPrevInstance表示當前例項的前一個例項的控制程式碼。
在Win32環境下,這個引數總是NULL,即在Win32環境下,這個引數不再起作用。
 
第三個引數lpCmdLine是一個以空終止的字串,指定傳遞給應用程式的命令列引數。
例如:一個d:/test.txt檔案,當用滑鼠雙擊它時將啟動記事本(notepad.exe),此時系統會將d:/test.txt作為命令列引數傳遞給記事本程式的WinMain函式,記事本程式在得到這個檔案的全路徑名後,就在視窗中顯示該檔案的內容。要在VC++開發環境中嚮應用程式傳遞引數,可以單擊選單Project --> Settings,選擇“Debug選項卡,在“Program arguments”編輯框中輸入你想傳遞給應用程式的引數。
 
第四個引數nCmdShow指定程式的視窗應該如何顯示,
例如最大化、最小化、隱藏等。這個引數的值由該程式的呼叫者所指定,應用程式通常不需要去理會這個引數的值。
 
關於WinMain函式前的修飾符WINAPI。者可以利用goto definition功能檢視WINAPI的定義,可以看到WINAPI其實就是__stdcall。
 

5.  視窗的建立

一個完整的視窗具有許多特徵,包括游標(滑鼠進入該視窗時的形狀)、圖示、背景色等。在建立一個視窗前,也必須對該型別的視窗進行設計,指定視窗的特徵。視窗的特徵就是由WNDCLASS結構體來定義的。WNDCLASS結構體的定義如下
typedef struct _WNDCLASS {
     UINT
           style
;
     WNDPROC
       lpfnWndProc
;
     int
           cbClsExtra
;
     int
           cbWndExtra
;
     HANDLE
        hInstance
;
    HICON
          hIcon
;
    HCURSOR
        hCursor
;
    HBRUSH
         hbrBackground
;
    LPCTSTR
        lpszMenuName
;
    LPCTSTR
        lpszClassName
;
} WNDCLASS;
第一個成員變數style指定這一型別視窗的樣式,常用的樣式如下:
CS_HREDRAW --當視窗水平方向上的寬度發生變化時,將重新繪製整個視窗。
CS_VREDRAW --當視窗垂直方向上的高度發生變化時,將重新繪製整個視窗。
CS_NOCLOSE --禁用系統選單的Close命令,這將導致視窗沒有關閉按鈕。
CS_DBLCLKS --當使用者在視窗中雙擊滑鼠時,向視窗過程傳送滑鼠雙擊訊息。
 

[知識點] Windows.h中,以CS_開頭的類樣式(Class Style)識別符號被定義為16位的常量,這些常量都只有某1位為1。在VC++開發環境中,用goto definition,可以看到CS_VREDRAW=0x0001CS_HREDRAW=0x0002CS_DBLCLKS =0x0008CS_NOCLOSE=0x0200,可將這些16進位制數轉換為2進位制數,就發現它們都只有1位為1,並且為1的位各不相同。用這種方式定義的識別符號稱為“位標誌”,可以用位運算操作符來組合使用這些樣式。

例如,要讓視窗在水平和垂直尺寸發生變化時發生重繪,可以使用位或(|)style=CS_HREDRAW | CS_VREDRAW

例如,要去掉先前的style變數所具有的CS_VREDRAW樣式,可以使用取反(~符進行操作,再和這個變數進行與(&)操作即可實現。如style=style & ~ CS_VREDRAW

 

 

 

 

第二個成員變數lpfnWndProc是一個函式指標,指向視窗過程函式,視窗過程函式是一個回撥函式,一個Windows程式可以包含多個視窗過程函式,一個視窗過程總是與某一個特定的視窗類相關聯(通過WNDCLASS結構體中的lpfnWndProc成員變數指定),基於該視窗過程。

 

lpfnWndProc成員變數的型別是WNDPROC,我們在VC++開發環境中使用goto definition功能,可以看到WNDPROC的定義:
typedef LRESULT (CALLBACK* WNDPROC)(HWND, UINT, WPARAM, LPARAM);
在這裡又出現了兩個新的資料型別LRESULT和CALLBACK,再次使用goto definition,可以看到它們實際上是long和__stdcall。
 
[知識點]  在函式呼叫過程中,會使用棧。__stdcall__cdecl是兩種不同的函式呼叫約定,定義了函式引數入棧的順序,由呼叫函式還是被呼叫函式將引數彈出棧,以及產生函式修飾名的方法。Win32API函式都遵循__stdcall呼叫約定。在VC++開發環境中,預設的編譯選項是__cdecl,對於那些需要__stdcall呼叫約定的函式,在宣告時必須顯式地加上__stdcall。在Windows程式中,回撥函式必須遵循__stdcall呼叫約定,所以我們在宣告回撥函式時要使用CALLBACK。使用CALLBACK而不是__stdcall的原因是為了告訴我們這是一個回撥函式。
 
 
第三個成員變數cbClsExtra:Windows為系統中的每一個視窗類管理一個WNDCLASS結構。在應用程式註冊一個視窗類時,它可以讓Windows系統為WNDCLASS結構分配和追加一定位元組數的附加記憶體空間,這部分記憶體空間稱為類附加記憶體,由屬於這種視窗類的所有視窗所共享,類附加記憶體空間用於儲存類的附加資訊。Windows系統把這部分記憶體初始化為0。一般我們將這個引數設定為0。
 
第四個成員變數cbWndExtra:Windows系統為每一個視窗管理一個內部資料結構,在註冊一個視窗類時,應用程式能夠指定一定位元組數的附加記憶體空間,稱為視窗附加記憶體。在建立這類視窗時,Windows系統就為視窗的結構分配和追加指定數目的視窗附加記憶體空間,應用程式可用這部分記憶體儲存視窗特有的資料。Windows系統把這部分記憶體初始化為0。如果應用程式用WNDCLASS結構註冊對話方塊(用資原始檔中的CLASS偽指令建立),必須給DLGWINDOWEXTRA設定這個成員。一般我們將這個引數設定為0。
 
第五個成員變數hInstance指定包含視窗過程的程式的例項控制程式碼。
 
第六個成員變數hIcon指定視窗類的圖示控制程式碼。這個成員變數必須是一個圖示資源的控制程式碼,如果這個成員為NULL,那麼系統將提供一個預設的圖示。
在為hIcon變數賦值時,可以呼叫LoadIcon函式來載入一個圖示資源,返回系統分配給該圖示的控制程式碼。該函式的原型宣告如下所示:
HICON LoadIcon( HINSTANCE hInstance, LPCTSTR lpIconName)
LoadIcon函式不僅可以載入Windows系統提供的標準圖示到記憶體中,還可以載入由使用者自己製作的圖示資源到記憶體中,並返回系統分配給該圖示的控制程式碼。但要注意的是,如果載入的是系統的標準圖示,那麼第一個引數必須為NULL
LoadIcon的第二個引數是LPCTSTR型別,利用goto definition命令將會發現它實際被定義成CONST CHAR*,即指向字元常量的指標,而圖示的ID是一個整數。對於這種情況我們需要用MAKEINTRESOURCE巨集把資源ID識別符號轉換為需要的LPCTSTR型別。
 
[知識點]  VC++中,對於自定義的選單、圖示、游標、對話方塊等資源,都儲存在資源指令碼*.rc檔案中。資源是通過識別符號(ID)來標識的,同一個ID可以標識多個不同的資源。資源的ID實質上是一個整數,在“resource.h”中定義為一個巨集。在為資源指定ID時,應該養成一個良好的習慣,即在“ID”後附加特定資源英文名稱的首字母,例如,選單資源為IDM_XXXM表示Menu),圖示資源為IDI_XXXI表示Icon),按鈕資源為IDB_ XXXB表示Button)。
 
第七個成員變數hCursor指定視窗類的游標控制程式碼。這個成員變數必須是一個游標資源的控制程式碼,如果這個成員為NULL,那麼無論何時滑鼠進入到應用程式視窗中,應用程式都必須明確地設定游標的形狀。
在為hCursor變數賦值時,可以呼叫LoadCursor函式來載入一個游標資源,返回系統分配給該游標的控制程式碼。該函式的原型宣告如下所示:
HCURSOR LoadCursor(HINSTANCE hInstance, LPCTSTR lpCursorName);
LoadCursor函式除了載入的是游標外,其使用方法與LoadIcon函式一樣。
 
第八個成員變數hbrBackground指定視窗類的背景畫刷控制程式碼。當視窗發生重繪時,系統使用這裡指定的畫刷來擦除視窗的背景。既可以為hbrBackground成員指定一個畫刷的控制程式碼,也可以為其指定一個標準的系統顏色值。
可以呼叫GetStockObject函式來得到系統的標準畫刷。GetStockObject函式的原型宣告如下所示:
HGDIOBJ GetStockObject( int fnObject);
引數fnObject指定要獲取的物件的型別。GetStockObject函式不僅可以用於獲取畫刷的控制程式碼,還可以用於獲取畫筆、字型和調色盤的控制程式碼。由於GetStockObject函式可以返回多種資源物件的控制程式碼,在實際呼叫該函式前無法確定它返回哪一種資源物件的控制程式碼,因此它的返回值的型別定義為HGDIOBJ,在實際使用時,需要進行型別轉換。例如,我們要為hbrBackground成員指定一個黑色畫刷的控制程式碼,可以呼叫如下:
wndclass.hbrBackground=(HBRUSH)GetStockObject(BLACK_BRUSH);
當視窗發生重繪時,系統會使用這裡指定的黑色畫刷擦除視窗的背景。
 
第九個成員變數lpszMenuName是一個以空終止的字串,指定選單資源的名字。如果你使用選單資源的ID號,那麼需要用MAKEINTRESOURCE巨集來進行轉換。如果將lpszMenuName成員設定為NULL,那麼基於這個視窗類建立的視窗將沒有預設的選單。要注意,選單並不是一個視窗,很多初學者都誤以為選單是一個視窗。
 
第十個成員變數lpszClassName是一個以空終止的字串,指定視窗類的名字。設計了一種新型別的視窗,要為該型別的視窗取個名字。這裡我們將這種型別視窗的命名為“HelloWindow”,後面將看到如何使用這個名稱。
 
 
6.  視窗的建立
設計完視窗類(WNDCLASS)後,需要呼叫RegisterClass函式對其進行註冊,註冊成功後,才可以建立該型別的視窗。註冊函式的原型宣告如下:
ATOM RegisterClass(CONST WNDCLASS *lpWndClass);
該函式只有一個引數,即上一步驟中所設計的視窗類物件的指標。
設計好視窗類並且將其成功註冊之後,就可以用CreateWindow函式產生這種型別的視窗了。CreateWindow函式的原型宣告如下:
HWND CreateWindow(
     LPCTSTR lpClassName,    // pointer to registered class name
     LPCTSTR lpWindowName,   // pointer to window name
     DWORD dwStyle,          // window style
     int x,                   // horizontal position of window
     int y,                   // vertical position of window
     int nWidth,              // window width
     int nHeight,             // window height
     HWND hWndParent,        // handle to parent or owner window
     HMENU hMenu,             // handle to menu or child-window identifier
     HANDLE hInstance,       // handle to application instance
     LPVOID lpParam          // pointer to window-creation data
);
lpClassName指定視窗類的名稱,即我們在上一步驟設計一個視窗類中為WNDCLASS的lpszClassName成員指定的名稱,在這裡應該設定為“HelloWindow”,表示要產生“HelloWindow”這一型別的視窗。
引數lpWindowName指定視窗的名字。如果視窗樣式指定了標題欄,那麼這裡指定的視窗名字將顯示在標題欄上。
引數dwStyle指定建立的視窗的樣式。要注意區分WNDCLASS中的style成員與CreateWindow函式的dwStyle引數,前者是指定視窗類的樣式,基於該視窗類建立的視窗都具有這些樣式,後者是指定某個具體的視窗的樣式。
在這裡,我們可以給建立的視窗指定WS_OVERLAPPEDWINDOW這一型別,該型別的定義為:
#define WS_OVERLAPPEDWINDOW (WS_OVERLAPPED  |  WS_CAPTION    |    
                                                                     WS_SYSMENU   |   WS_THICKFRAME  |
                                                                     WS_MINIMIZEBOX     |    WS_MAXIMIZEBOX)
可以看到,WS_OVERLAPPEDWINDOW是多種視窗型別的組合。下面是這幾種常用視窗型別的說明。
 WS_OVERLAPPED:產生一個層疊的視窗,一個層疊的視窗有一個標題欄和一個邊框。
WS_CAPTION:建立一個有標題欄的視窗。
WS_SYSMENU:建立一個在標題欄上帶有系統選單的視窗,要和WS_CAPTION型別一起使用。
WS_THICKFRAME:建立一個具有可調邊框的視窗。
WS_MINIMIZEBOX:建立一個具有最小化按鈕的視窗,必須同時設定WS_ SYSMENU型別。
WS_MAXIMIZEBOX:建立一個具有最大化按鈕的視窗,必須同時設定WS_ SYSMENU型別。
使用WS_OVERLAPPEDWINDOW型別的視窗如圖1.1所示。
CreateWindow函式的引數xy,nWidth,nHeight分別指定視窗左上角的xy座標,視窗的寬度,高度。如果引數x被設為CW_USEDEFAULT,那麼系統為視窗選擇預設的左上角座標並忽略y引數。如果引數nWidth被設為CW_USEDEFAULT,那麼系統為視窗選擇預設的寬度和高度,引數nHeight被忽略。
引數hWndParent指定被建立視窗的父視窗控制程式碼。視窗之間可以有父子關係,子視窗必須具有WS_CHILD樣式。對父視窗的操作同時也會影響到子視窗,表1.1列出了對父視窗的操作如何影響子視窗。
表1.1  對父視窗的操作對子視窗的影響
  
  
銷燬
在父視窗被銷燬之前銷燬
隱藏
在父視窗被隱藏之前隱藏,子視窗只有在父視窗可見時可見
移動
跟隨父視窗客戶區一起移動
顯示
在父視窗顯示之後顯示
引數hMenu指定視窗選單的控制程式碼。
引數hInstance指定視窗所屬的應用程式例項的控制程式碼。
引數lpParam:作為WM_CREATE訊息的附加引數lParam傳入的資料指標。在建立多文件介面的客戶視窗時,lpParam必須指向CLIENTCREATESTRUCT結構體。多數視窗將這個引數設定為NULL。
如果視窗建立成功,CreateWindow函式將返回系統為該視窗分配的控制程式碼,否則,返回NULL。注意,在建立視窗之前應先定義一個視窗控制程式碼變數來接收建立視窗之後返回的控制程式碼值。
7.顯示及更新視窗
(1)顯示視窗
視窗建立之後,我們要讓它顯示出來。呼叫函式ShowWindow來顯示視窗,該函式的原型宣告如下所示:
BOOL ShowWindow(
  HWND hWnd,     // handle to window
  int nCmdShow   // show state
);
ShowWindow函式有兩個引數,第一個引數hWnd就是在上一步驟中成功建立視窗後返回的那個視窗控制程式碼;第二個引數nCmdShow指定了視窗顯示的狀態,常用的有以下幾種。
SW_HIDE:隱藏視窗並啟用其他視窗。
SW_SHOW:在視窗原來的位置以原來的尺寸啟用和顯示視窗。
SW_SHOWMAXIMIZED:啟用視窗並將其最大化顯示。
SW_SHOWMINIMIZED:啟用視窗並將其最小化顯示。
SW_SHOWNORMAL:啟用並顯示視窗。如果視窗是最小化或最大化的狀態,系統將其恢復到原來的尺寸和大小。應用程式在第一次顯示視窗的時候應該指定此標誌。
(2)更新視窗
在呼叫ShowWindow函式之後,我們緊接著呼叫UpdateWindow來重新整理視窗,就好像我們買了新房子,需要裝修一下。UpdateWindow函式的原型宣告如下:
BOOL UpdateWindow(
  HWND hWnd   // handle to window
);
其引數hWnd指的是建立成功後的視窗的控制程式碼。UpdateWindow函式通過傳送一個WM_PAINT訊息來重新整理視窗,UpdateWindow將WM_PAINT訊息直接傳送給了視窗過程函式進行處理,而沒有放到我們前面所說的訊息佇列裡,注意這一點。到此,一個視窗就算建立完成了。

相關文章