Windows程式內部運行機制執行個體詳解_C 語言

來源:互聯網
上載者:User

本文以孫鑫老師VC++教程中的程式為基礎,詳細講解了Windows程式內部運行機制,相信可以協助大家更好的理解Windows程式運行原理及相應的VC++程式設計。具體內容如下:

建立一個Win32應用程式步驟:

1、編寫WinMain函數;

2、建立視窗(步驟如下):

 a、設計(一個)視窗類別(WNDCLASS)

 b、註冊(該)視窗類別。

 c、建立視窗。

 d、顯示並更新視窗。

3、編寫訊息迴圈。

4、編寫視窗過程函數。

//WinMain.cpp#include <windows.h>#include <stdio.h>LRESULT CALLBACK WinAzeProc(   HWND hwnd,   // handle to window   UINT uMsg,   // message identifier   WPARAM wParam, // first message parameter   LPARAM lParam  // second message parameter   );int WINAPI WinMain(          HINSTANCE hInstance,   // handle to current instance          HINSTANCE hPrevInstance, // handle to previous instance          LPSTR lpCmdLine,     // command line          int nCmdShow       // show state          ){  //設計一個視窗類別  WNDCLASS wndcls;  wndcls.cbClsExtra = 0;  wndcls.cbWndExtra = 0;  wndcls.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);  wndcls.hCursor = LoadCursor(NULL, IDC_CROSS);  wndcls.hIcon = LoadIcon(NULL, IDI_ERROR);  wndcls.hInstance = hInstance;  //應用程式執行個體控制代碼由WinMain函數傳進來  wndcls.lpfnWndProc = WinAzeProc;  wndcls.lpszClassName = "aze_003";  wndcls.lpszMenuName = NULL;  wndcls.style = CS_HREDRAW | CS_VREDRAW;  RegisterClass(&wndcls);  //註冊視窗類別    //建立視窗,定義一個變數用來儲存成功建立後返回的控制代碼  HWND hwnd;    hwnd = CreateWindow("aze_003", "first Application", WS_OVERLAPPEDWINDOW, 0, 0, 600, 500, NULL, NULL,hInstance, NULL);  ShowWindow(hwnd, SW_SHOWNORMAL);  //顯示視窗  UpdateWindow(hwnd);    //重新整理視窗  //定義訊息結構體,開始訊息迴圈  MSG msg;  while( GetMessage(&msg, NULL, 0, 0) )  {    TranslateMessage(&msg);    DispatchMessage(&msg);  }  return msg.wParam;}//編寫視窗過程函數LRESULT CALLBACK WinAzeProc(   HWND hwnd,   // handle to window   UINT uMsg,   // message identifier   WPARAM wParam, // first message parameter   LPARAM lParam  // second message parameter   ){  switch(uMsg)  {  case WM_CHAR:    char szChar[20];    sprintf(szChar, "char code is %d", wParam);    MessageBox(hwnd, szChar, "char", 0);    break;  case WM_LBUTTONDOWN:    MessageBox(hwnd, "mouse clicked", "message", 0);    HDC hdc;    hdc = GetDC(hwnd);    //不能在響應WM_PAINT訊息時調用    TextOut( hdc, 0, 50, "程式員之家!",strlen("程式員之家!") );    ReleaseDC(hwnd, hdc);    break;  case WM_PAINT:    HDC hDC;    PAINTSTRUCT ps;    hDC = BeginPaint(hwnd, &ps);  //BeginPaint只能在響應WM_PAINT訊息是調用    TextOut(hDC, 0, 0, "http://www.sunxin.org", strlen("http://www.sunxin.org"));    EndPaint(hwnd, &ps);    break;  case WM_CLOSE:    if( IDYES == MessageBox(hwnd, "是否真的退出?", "message", MB_YESNO) )    {      DestroyWindow(hwnd);    }    break;  case WM_DESTROY:    PostQuitMessage(0);    break;  default:    return DefWindowProc(hwnd, uMsg, wParam, lParam);  }  return 0;}

程式運行後顯示介面如下:

視窗分為客戶區(是視窗的一部分)與非客戶區。

標題列、功能表列、系統功能表、最小(大)化框、可調邊框統稱為視窗的非客戶區,由Windows系統管理;應用程式主要管理客戶區的外觀及操作(顯示文字、繪製圖形)。

對話方塊、訊息框也是一種視窗;對話方塊上還包括許多子視窗:按鈕、選項按鈕、複選框、組狂、文本編輯框等。

2、視窗與控制代碼:

在Windows應用程式中,視窗是通過視窗控制代碼(HWND)來標識的;要對某個視窗進行操作,就必須要得到這個視窗的控制代碼。

控制代碼是Windows程式中一個重要的概念(表徵圖控制代碼(HICON)、游標控制代碼(HCURSOR)、畫刷控制代碼(HBRUSH))。

3、訊息與訊息佇列:

Windows程式設計模式是一種事件驅動方式的程式設計模式,主要是基於訊息的。(當系統感知到一事件時(如點擊滑鼠),系統會將這個事件封裝成一個訊息,投遞到應用程式的訊息佇列中,然後應用程式從訊息佇列中取出訊息並進行響應。在這個處理過程中,作業系統也會給應用程式“發送訊息”。“發送訊息”:實際指:作業系統調用程式中一個負責處理訊息的視窗過程函數)

(1)訊息:Windows中,訊息由MSG結構體表示,如下: 

//The MSG structure contains message information from a thread's message queue. typedef struct tagMSG { HWND  hwnd;     //訊息所屬的視窗,訊息都是與視窗相關聯的 UINT  message;   //the message identifier WPARAM wParam;    //指定訊息的附加訊息 LPARAM lParam;    //指定訊息的附加訊息 DWORD time;     //訊息投遞到隊列中的時間 POINT pt;      //滑鼠的當前位置} MSG, *PMSG;

Windows中,訊息是由一個個數值表示的;Windows將訊息對應的數值定義為WM_XXX宏(WM:Window Message)的形式,XXX對應某種訊息的英文拼字的大寫形式。如:WM_LBUTTONDOWN:滑鼠左鍵按下訊息、WM_KEYDOWN:鍵盤按下訊息、WM_CHAR:字元訊息···

(2)訊息佇列:每一個Windows應用程式開始執行後,系統都會為改程式建立一個訊息佇列,這個訊息佇列用來存放改程式建立的視窗的訊息。

(3)進隊訊息 與 不進隊訊息:

      進隊的訊息將由系統放入到應用程式的訊息佇列中,然後由應用程式取出並發送;

      不進隊訊息在系統調用視窗過程時,直接發送給視窗;

      兩者最終都是有系統調用視窗過程函數對訊息進行處理。

4、WinMain函數

(一)MSDN上的WinMain函數定義如下(備有詳盡的注釋):

//The WinMain function is called by the system as the initial entry point for a Windows-based application. int WINAPI WinMain( HINSTANCE hInstance,   // handle to current instance當前視窗控制代碼 HINSTANCE hPrevInstance, // handle to previous instance前一個開啟的視窗控制代碼 LPSTR lpCmdLine,     // command line 指定傳遞給應用程式的*命令列*參數 int nCmdShow       // show state 指定視窗應該如何顯示,如:最大(小)化、隱藏等);

(二)視窗類別的結構體的定義:

(1)本文程式中對應代碼如下:

typedef struct _WNDCLASS {   UINT    style;    //指定*這一類型*視窗的樣式,如:CS_HREDRAW、CS_VREDRAW、CS_NOCLOSE、CS_DBLCLKS  WNDPROC  lpfnWndProc;   //函數指標,指向視窗過程函數(視窗過程函數是一回呼函數)  int    cbClsExtra;   //一般為0  int    cbWndExtra;   //同上  HINSTANCE hInstance;   //指定包含視窗過程的程式的執行個體控制代碼  HICON   hIcon;     //指定視窗類別的表徵圖控制代碼  HCURSOR  hCursor;   //指定視窗類別的游標控制代碼  HBRUSH   hbrBackground;   //指定視窗類別的背景畫刷控制代碼;當視窗發生重繪值,系統使用這裡指定的畫刷來查處視窗的背景  LPCTSTR  lpszMenuName;   //指定菜單資源的名字 **菜單並不是一個視窗**  LPCTSTR  lpszClassName;   //指定視窗類別的名字} WNDCLASS, *PWNDCLASS;

回呼函數不是由該函數的實現方直接調用,而是在特定的事件或條件發生時有另一方調用的,用於該事件或條件進行響應。

回呼函數的實現機制是:

    ①定義一個回呼函數。

    ②提供函數實現的一方在初始化的時候,將回呼函數的函數指標註冊給調用者。

    ③當特定的事件或條件發生的時候,調用者使用函數指標調用回呼函數對事件進行處理。

針對Windows的訊息處理機制,視窗過程函數被調用的過程如下:

    ①在設計視窗類別的時候,將視窗過程函數的地址賦值給lpfnWndProc成員變數;

    ②調用RegisterClass(&wndclass)註冊視窗類別,那麼系統就有了我們所編寫的視窗過程函數的地址。

    ③當應用程式接收到某一視窗的訊息時,調用DispatchMessage(&msg)將對訊息回傳給系統。系統則利用先前註冊視窗類別時得到的函數指標,調用視窗過程函數對訊息進行處理。

提示:一個Windows程式可以包含多個視窗過程函數,一個視窗過程總是與某一個特定的視窗類別相關聯(通過WNDCLASS結構體中的lpfnWndProc成員變數指定),基於該視窗類別建立的視窗使用同一個視窗過程。

lpfnWndProc成員變數的類型是WNDPROC,定義如下:

typedef LRESULT (CALLBACK* WNDPROC)(HWND, UINT, WPARAM, LPARAM); //LRESULT=long, CALLBACK=_stdcall WNDPROC是函數指標類型。

注意:WNDPROC被定義為指向視窗過程函數的指標類型,視窗過程函數的格式必須與WNDPROC相同。

在VC++中,資源是通過標識符(ID)來標識的,同一個ID可以標識多個不同的資源(資源的ID本質上是一個整數)。如:菜單資源:IDM_XXX(M表示Menu)、表徵圖資源:IDI_XXX(I表示表徵圖)、按鈕資源:IDB_XXX(B表示Button)

可以調用GetStockObject(int fnObject) 來得到系統的標準畫刷。聲明如下:

//The GetStockObject function retrieves a handle to one of the stock pens, brushes, fonts, or palettes. HGDIOBJ GetStockObject( int fnObject  // stock object type);

GetStockObject函數:返回多種資來源物件的控制代碼,如:畫刷、畫筆、字型、調色盤等;

函數返回時,需進行類型轉換。如:

wndcls.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); 

(2)註冊視窗類別:設計視窗類別(WNDCLASS)後,需要調用RegisterClass函數對其進行註冊,註冊成功後,才可以建立該類型的視窗。聲明如下:

ATOM RegisterClass( CONST WNDCLASS *lpWndClass // class data, 視窗類別對象的指標               // Pointer to a WNDCLASS structure. You must fill the structure with the appropriate class attributes before passing it to the function. );

(3)建立視窗:CreateWindow函式宣告如下:

HWND CreateWindow( LPCTSTR lpClassName, // registered class name 即:視窗類別WNDCLASS的lpszClassName成員指定的名稱(必須先註冊) LPCTSTR lpWindowName, // window name  指定視窗的名字 DWORD dwStyle,    // window style 指定建立視窗的樣式 如:WS_OVERLAPPEDWINDOW 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,     // menu handle or child identifier HINSTANCE hInstance, // handle to application instance LPVOID lpParam    // window-creation data 作為WM_CREATE訊息的附加參數lParam傳入的資料指標(一般為:NULL));

如果視窗建立成功,CreateWindow函數將返回系統為該視窗分配的控制代碼;否則,返回NULL。

·注意:在建立視窗之前應先定義一個視窗控制代碼變數來接收建立視窗之後的控制代碼值。

顯示及更新視窗:

(4)顯示視窗:ShowWindow聲明如下:

BOOL ShowWindow( HWND hWnd,   // handle to window 該參數為成功建立視窗後返回的那個視窗控制代碼 int nCmdShow  // show state 如:SW_HIDE、SW_SHOW、SW_SHOWNORMAL、SW_SHOWMINIMIZED、SW_SHOWMAXIMIZED··);

(5)更新(重新整理)視窗:UpdateWindow函式宣告原型如下:

BOOL UpdateWindow( HWND hWnd  // handle to window 建立成功後的視窗控制代碼);

UpdateWindow函數通過發送一個WM_PAINT訊息來重新整理視窗,UpdateWindow將WM_PAINT訊息直接發送給了視窗過程函數進行處理,而沒有放到訊息佇列裡面。

(三)、訊息迴圈

視窗 建立、顯示、更新後;需要編寫一個訊息迴圈,不斷的從訊息佇列中取出訊息,並進行響應。

GetMessage()函數:從訊息佇列中取出訊息

BOOL GetMessage( LPMSG lpMsg,     // message information 指向一個訊息(MSG)結構體,GetMessage從線程的訊息佇列中取出的訊息資訊將儲存在該結構體對象中 HWND hWnd,      // handle to window 指定接收屬於哪一個視窗的訊息;NULL:用於接收屬於調用線程的所有視窗的視窗訊息 UINT wMsgFilterMin, // first message 指定擷取打的訊息的最小值 UINT wMsgFilterMax  // last message 如果wMsgFilterMin=0和wMsgFilterMax=0,則接收所有訊息);

GetMessage函數接收到除WM_QUIT外的訊息均返回非零值。

//訊息迴圈代碼,一般形式  MSG msg;  while( GetMessage(&msg, NULL, 0, 0) )  {    TranslateMessage(&msg); //TranslateMessage函數將虛擬鍵訊息*轉換*為字元訊息,被投遞到調用線程的訊息佇列中,當下一次調用GetMessage函數時被取出    DispatchMessage(&msg);  //DispatchMessage函數指派一個訊息到視窗過程,有視窗過程函數對訊息進行處理//DispatchMessage實際上是將訊息會傳給作業系統,有作業系統調用視窗過程函數對訊息進行處理(響應)  }

Windows應用程式的訊息處理機制如下圖所示:

Windows應用程式的訊息處理過程:

    (1)作業系統就收到應用程式的視窗訊息,將訊息投遞到該應用程式的訊息佇列中。

    (2)應用程式在訊息迴圈匯總調用GetMessage函數從訊息佇列中取出一條一條的訊息。取出訊息後,應用程式可以對訊息進行一些預先處理,如:放棄對某些訊息的響應,或者調用TranslateMessage產生新的訊息。

    (3)應用程式調用DisPatchMessage,將訊息回傳給作業系統。訊息是由MSG結構體對象來表示的,其中就包含了接收訊息的視窗的控制代碼。故:DisPatchMessage函數總能進行正確的傳遞。

    (4)操作利用WNDCLASS結構體的lpfnWndProc成員儲存的視窗過程函數的指標調用視窗過程,對訊息進行處理(即“系統給應用程式發送了訊息”)。

補充:

  (1)從訊息佇列中擷取訊息還可以調用PeekMessage函數,函數原型如下:

BOOL PeekMessage( LPMSG lpMsg,     // message information HWND hWnd,      // handle to window UINT wMsgFilterMin, // first message UINT wMsgFilterMax, // last message UINT wRemoveMsg   // removal options);

前四個參數與GetMessage函數的參數作用相同;

最後一個參數指定訊息擷取的方式;如果設為PM_NOREMOVE, 那麼訊息將不會從訊息佇列中被移除;如果設為PM_REMOVE, 那麼訊息將從訊息佇列中被移除(與GetMessage函數的行為一致)

  (2)發送訊息可以使用SendMessage和PostMessage函數。

      SendMessage將訊息直接發送給視窗,並調用該視窗的視窗過程進行處理;在視窗過程對訊息處理完畢後,該函數才返回(SendMessage發送的訊息為不進隊訊息)。

      PostMessage函數將訊息放入與建立視窗的線程相關聯的訊息佇列後立即返回。

      PostThreadMessage函數,用於向線程發送訊息。

      對於線程訊息,MSG結構體中的hwnd成員為NULL。

(四)、編寫視窗過程函數:用於處理髮送給視窗的訊息

LRESULT CALLBACK WindowProc(  //視窗過程函數的名字可以隨便取,如:WinAzeProc,但函式宣告與定義要一致; HWND hwnd,   // handle to window UINT uMsg,   // message identifier 訊息代碼 WPARAM wParam, // first message parameter 訊息代碼的兩個附加值 LPARAM lParam  // second message parameter);

提示:系統通過視窗過程函數的地址(指標)來調用視窗過程函數,而不是名字。

//編寫視窗過程函數LRESULT CALLBACK WinAzeProc(              HWND hwnd,   // handle to window              UINT uMsg,   // message identifier              WPARAM wParam, // first message parameter              LPARAM lParam  // second message parameter              ){  switch(uMsg)  {  case WM_CHAR:  //通過調用TranslateMessage函數轉換得到    char szChar[20];    sprintf(szChar, "char code is %d", wParam);    MessageBox(hwnd, szChar, "char", 0);    break;  case WM_LBUTTONDOWN:    MessageBox(hwnd, "mouse clicked!", "message", 0);    HDC hdc;    hdc = GetDC(hwnd);    //用hdc儲存GetDC函數返回的與特定視窗相關聯的DC的控制代碼。                //GetDC()不能在響應WM_PAINT訊息時調用    TextOut( hdc, 0, 50, "程式員之家!",strlen("程式員之家!") );  //TextOut利用得到的DC控制代碼在指定的位置(0,50)出輸出一行文字    ReleaseDC(hwnd, hdc);  //釋放hdc    break;  case WM_PAINT:  //當視窗客服區的一部分或者全部變為“無效”時,系統會發送WM_PAINT訊息,通知應用程式重新繪製視窗          //視窗剛建立時,客戶區是無效狀態,當調用UpdateWindow函數時,會發送WM_PAINT訊息給視窗過程,對視窗進行重新整理          //當視窗從無到有、改變尺寸、最小化在恢複、被其他視窗遮蓋後在顯示時,視窗的客戶區都將變為無效,此時系統會給應用程式發送WM_PAINT訊息,通知應用程式重新繪製          //提示:視窗大小發生變化時,是否發生重繪,取決於WNDCLASS結構體中style成員是否設定了CS_HREDRAW和CS_VREDRAW標誌    HDC hDC;    PAINTSTRUCT ps;    //ps用於接收繪製的資訊    hDC = BeginPaint(hwnd, &ps);  //BeginPaint只能在響應WM_PAINT訊息是調用    TextOut(hDC, 0, 0, "http://www.sunxin.org", strlen("http://www.sunxin.org"));     EndPaint(hwnd, &ps);    break;  case WM_CLOSE:    if( IDYES == MessageBox(hwnd, "是否真的退出?", "message", MB_YESNO) )    {      DestroyWindow(hwnd);    }    break;  case WM_DESTROY:    PostQuitMessage(0);    break;  default:    return DefWindowProc(hwnd, uMsg, wParam, lParam); //DefWindowProc調用預設的視窗過程,對應用程式沒有處理的其他訊息提供預設處理。//對於大多數的訊息,應用程式可以直接調用DefWindowProc函數進行處理。//在編寫視窗過程時,應將DefWindowProc函數的調用放到default語句中,並將該函數的傳回值作為視窗過程函數的傳回值。  }  return 0;}

提示:要在視窗中輸出文字或者顯示圖形,需要用到裝置描述表(Device ConText)。

裝置描述表(簡稱DC):

DC是一個包含裝置(物理輸出裝置,如顯示器、裝置磁碟機)資訊的結構體,在Windows平台下,所有的圖形操作都是利用DC來完成的。

第30、31行代碼:在調用BeginPaint時,如果客戶區的背景還沒有被擦除,那麼BeginPaint會發送WM_ERASEBKGND訊息給視窗,系統就會使用WNDCLASS結構體的hbrBackGround成員指定的畫刷來擦除背景。如果我們想要讓某個圖形時鐘在視窗中顯示,就應該將圖形的繪製操作放到響應WM_PAINT訊息的代碼中,如TextOut()的位置。

第34-48行代碼:DestroyWindow函數在銷毀視窗後會向視窗過程發送WM_DESTROY訊息。注意:此時視窗雖然銷毀了,但應用程式並沒有退出。故:如果自己要控製程序是否退出,應該在WM_CLOSE訊息的響應代碼中完成。

   對WM_CLOSE訊息的響應並不是必須的,如果應用程式沒有對該訊息進行響應,系統將把這條訊息傳給DefWindowProc函數,而DefWindowProc函數則條用DestroyWindow函數來響應 這條WM_CLOSE訊息。

第40-42行代碼:DestroyWindow函數在銷毀視窗後,會給視窗過程發送WM_DESTROY訊息, 然後在該訊息的響應代碼中調用PostQuitMessage函數。PostQuitMessage函數項應用程式的訊息佇列中投遞一條WM_QUIT訊息並返回。GetMessage函數只有在收到WM_QUIT訊息時才返回0,此時訊息迴圈才結束,程式退成。

  想讓程式正常退出,我們必須響應WM_DESTROY訊息,並在訊息響應代碼中調用PostQuitMessage,嚮應用程式的訊息佇列中投遞WM_QUIT訊息。傳遞給PostQuitMessage函數的參數值將作為WM_QUIT訊息的wParam參數,這個值通常用做WinMain函數的傳回值。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.