windows編程 第二回 windows程式的生與死(上)

來源:互聯網
上載者:User

-----路過的朋友,若發現錯誤或有好的建議,歡迎在下面留言,謝謝!-----

引子

                     

      “Windows 程式分為‘程式碼’和‘UI(User Interface)資源’兩大部份,兩部份最後以RC編譯器(資源編譯器)整合為一個完整的EXE 檔案。所謂UI 資源是指功能菜單、對話方塊外貌、程式表徵圖、游標形狀等等東西。這些UI 資源的實際內容(二進位代碼)系藉助各種工具產生,並以各種副檔名存在,如.ico、.bmp、.cur 等等。程式員必須在一個所謂的資源描述檔(.rc)中描述它們。RC 編譯器讀取RC 檔的描述後將所有UI資源檔集中製作出一個.RES 檔,再與程式碼結合在一起,這才是一個完整的Windows可執行件。”

      以上是侯捷先生在《深入淺出MFC》中關於windows程式開發流程的一段論述,我覺得他說的很是精闢,簡明扼要的說清楚了Windows程式的兩方面,特放在此與大家分享。下文中我還還要引用他的這本書中的內容。

我的忠告

      說到windows編程,我認為首先要先瞭解清楚windows程式由生到死的整個機制和過程。當你對微軟定的這個“遊戲規則”瞭然於胸後,我想後面的學習將會對你來說很輕鬆。所以,請你認真看懂這一回的內容。

      這兩回我要交代的東西還真是不少,既要講解windows程式機制又要引進windows中很多概念。在此我很欣賞王爽老師的教學觀點——知識屏蔽。有關概念等到用的時候我再解釋,不用的我先不提,很多書開始都是先一股腦的把很多概念都拋出來,讓讀者看的迷迷糊糊的,反而增加了讀者的畏懼心理。我認為一邊用一邊在介紹概念反而會讓讀者更好理解一點。(至少對於我來說是這樣)。

       我還要再強調一下,這兩回的的主要內容是講解運行機制,我打算以一邊講機制一邊讀程式的方式來進行。考慮到大家對很多概念和很多函數都是第一次接觸,所以我只是以“寫意”形式來搞。只要你對這機制和過程能宏觀和整體上掌握瞭解,那你就是成功的。遇到概念我會簡單介紹(小字內容是對概念的擴充,有餘力的讀者可以看看),看了不理解,這很正常,請繼續看下去,從下面我對概念的運用上你再慢慢體會這概念的含義,這個概念理解過程正如我們小的時候學說話,聽得多了自然就懂了(現在不必一時糾結於此)。很多函數我只說它的作用,具體用法和為什麼這麼用請你在這一回不要操心(函數用法我以後會講解,以後你看得多了,閉著眼都能寫出來,不必急於一時),以防分心從而破壞了對瞭解windows機制和程式有生到死過程的連貫性,請記住我們的目的 ——對windows機制和程式有生到死過程宏觀和整體瞭解,其他是浮雲,哈哈。

先來上一段代碼——請別害怕

 

1.#include <windows.h>2.LRESULT CALLBACK WinSunProc(3.HWND hwnd,      // handle to window4.UINT uMsg,      // message identifier5.WPARAM wParam,  // first message parameter6.LPARAM lParam   // second message parameter7.);8.int WINAPI WinMain(9.HINSTANCE hInstance,      // handle to current instance10.HINSTANCE hPrevInstance,  // handle to previous instance11.LPSTR lpCmdLine,          // command line12.int nCmdShow              // show state13.)14.{15.WNDCLASS wndcls;        //定義一個wndcls視窗類別對象16.wndcls.cbClsExtra=0;        //類變數佔用的儲存空間17.wndcls.cbWndExtra=0;       //執行個體變數佔用的儲存空間18.wndcls.hbrBackground=(HBRUSH)GetStockObject(BLACK_BRUSH);//指定視窗類別畫刷控制代碼19.wndcls.hCursor=LoadCursor(NULL,IDC_ARROW);   //指定視窗類別游標控制代碼20.wndcls.hIcon=LoadIcon(NULL,IDI_APPLICATION);  //指定視窗類別表徵圖控制代碼21.wndcls.hInstance=hInstance;                       //包含視窗過程的程式的執行個體控制代碼22.wndcls.lpfnWndProc=WinSunProc;                 //指向視窗過程函數23.wndcls.lpszClassName="Hello World";              //指定視窗類別名字24.wndcls.lpszMenuName=NULL;                    //指定菜單資源名字25.wndcls.style=CS_HREDRAW | CS_VREDRAW;      //指定視窗類別型樣式26.RegisterClass(&wndcls); //註冊視窗27.HWND hwnd;          //定義控制代碼變數28.hwnd=CreateWindow    //建立視窗,返回系統為視窗分配的控制代碼29.("Hello World",         //類名,指定該視窗所屬的類30."Hello World Program",  //視窗的名字,即在標題列中顯示的文本31.WS_OVERLAPPEDWINDOW, //該視窗的風格32.0,                             //視窗左上方相對於螢幕左上方的初始X座標33.0,                             //視窗左上方相對於螢幕左上方的初始Y座標34.600,                       //視窗的寬度35.400,                           //視窗的高度36.NULL,                        //一個子視窗的父視窗的控制代碼,或隸屬視窗的擁有者視窗的控制代碼37.NULL,                        //菜單控制代碼38.hInstance,                  //建立視窗對象的應用程式的執行個體控制代碼39.NULL);                       //建立視窗時指定的額外參數40.ShowWindow(hwnd,SW_SHOWNORMAL); 顯示視窗41.UpdateWindow(hwnd);更新視窗42.MSG msg;43.while(GetMessage(&msg,NULL,0,0))44.{45.TranslateMessage(&msg);46.DispatchMessage(&msg);47.}48.return msg.wParam;49.}50.LRESULT CALLBACK WinSunProc(51.HWND hwnd,      // handle to window52.UINT uMsg,      // message identifier53.WPARAM wParam,  // first message parameter54.LPARAM lParam   // second message parameter55.)56.{57.switch(uMsg)58.{59.case WM_CREATE:60.MessageBox(hwnd,"Window Created","message",MB_OK);61.break;62.case WM_PAINT:63.HDC hDC;64.PAINTSTRUCT ps;65.hDC=BeginPaint(hwnd,&ps);66.TextOut(hDC,0,0,"Hello World!",strlen("Hello World!"));67.EndPaint(hwnd,&ps);68.break;69.case WM_DESTROY:70.PostQuitMessage(0);71.break;72.default:73.return DefWindowProc(hwnd,uMsg,wParam,lParam);74.}75.return 0;76.}

  

      在Windows XP環境下開啟VC6.0(雖然有點老了,但對我們來說功能夠用了),從File菜單中選擇New,在Nem對話方塊中,單擊Project標籤,選擇Win32 Application。輸入Project Name與Location後點確定。在下一個出現的對話方塊選Empty Workspace,再按下Finish。進入項目後,再建立一個C++ Sourse File,將以上代碼貼上後,點擊BuildExecute按鈕後將出現一個提示框(),點擊確定後,會出現一個視窗()。

 

    

 

概念解釋

      視窗是螢幕上與一個應用程式相關的矩形地區,它是使用者與產生該視窗的應用程式之間的視覺化介面,他接收使用者的輸入,並以文本或圖形的格式顯示輸出內容。對應用程式來說,視窗是應用程式控制下的螢幕上的一個矩形區 域,應用程式建立並控制視窗的所有方面。當使用者啟動一個應用程式時,一個視窗就被建立。每當使用者操作視窗中的對象時,程式就有所響應。

      它一般由邊框、標題列、控制欄最小化表徵圖、最大化表徵圖、關閉表徵圖水平捲軸、垂直捲軸、功能表列和客戶區構成。①

      如常見的記事本就是一個很好的例子:

 

Windows程式啟動並執行機制——一訊息為基礎,事件驅動之(引用侯捷老師《深入淺出MFC》)

概念解釋:

    事件與訊息

    事件由使用者(操作電腦的人)觸發且只能由使用者觸發(如使用者按下了滑鼠按鍵,就產生一滑鼠事件),作業系統能夠感覺到由使用者觸發的事件,並將此事件轉換為一個(特定的)訊息發送到程式的訊息佇列中。②

 

      訊息佇列

      訊息被發送到隊列中。“訊息佇列”是在訊息的傳輸過程中儲存訊息的容器。訊息佇列管理器在將訊息從它的源中繼到它的目標時充當中間人。隊列的主要目的是提供路由並保證訊息的傳遞;如果發送訊息時接收者不可用,訊息佇列會保留訊息,直到可以成功地傳遞它。(來自百度百科)

 

       Windows 程式的進行系依靠外部發生的事件來驅動。換句話說,程式不斷等待(利用一個while 迴路),等待任何可能的輸入,然後做判斷,然後再做適當的處理。上述的“輸入”是由作業系統捕捉到之後,以訊息形式(一種資料結構)進入程式之中。作業系統如何捕捉外圍裝置(如鍵盤和滑鼠)所發生的事件呢?噢,USER 模組③(不懂先不管它)掌管各個外圍的驅動程式,它們各有偵測迴路。

      如果把應用程式獲得的各種“輸入”分類,可以分為由硬體裝置所產生的訊息(如滑鼠移動或鍵盤被按下),放在系統訊息佇列(system  message queue)中,以及由Windows 系統或其它Windows 程式傳送過來的訊息,放在程式訊息佇列(application message queue)中。以應用程式的眼光來看,訊息就是訊息,來自哪裡或放在哪裡其實並沒有太大區別,反正程式調用GetMessage API 就取得一個訊息,程式的生命靠它來推動。所有的GUI(Graphical User Interface,圖形化使用者介面) 系統,包括UNIX的X Window 以及OS/2 的Presentation Manager,都像這樣,是以訊息為基礎的事件驅動系統。

      可想而知,每一個Windows 程式都應該有一個迴路如下(先熟悉一下即可):

MSG msg;

while (GetMessage(&msg, NULL, NULL, NULL)) {

TranslateMessage(&msg);

DispatchMessage(&msg);

}

// 以上出現的函數都是Windows API 函數

僅瞭解這個while迴圈有可以不斷處理訊息的作用即可。

 

Windows程式生與死——開始分析代碼了!

      不要被上面的程式碼數嚇壞了,對就是這麼多行才產生一個視窗,沒辦法微軟就是這麼規定的,這個簡簡單單的視窗可以說是麻雀雖小,五髒俱全。但是幾乎所有的windows程式都是有它為骨架擴充而來的,弄懂它你就會了一半的windows基本編程,剩下的工作頂多就是學學函數,填充一下它罷了。

      一個程式的一生要經曆出生、運行、死亡三個階段,且聽我慢慢道來……

出生:

     正如在C程式中的進入點是函數main一樣,Windows程式的進入點是WinMain,總是像這樣出現:見行8 (再強調一下,只講函數作用,具體用法參數含義後面講)

引用孫鑫老師的精彩講解

“建立一個完整的視窗,需要經過下面幾個操作步驟:

設計一個視窗類別;

註冊視窗類別;

建立視窗;

顯示及更新視窗。

(請先記住這個順序,先不要問為什麼這樣)

1.設計一個視窗類別

     一個完整的視窗具有許多特徵,包括游標(滑鼠進入該視窗時的形狀)、表徵圖、背景色等。視窗的建立過程類似於汽車的製造過程。我們在生產一個型號的汽車之前,首先要對該型號的汽車進行設計,在圖紙上畫出汽車的結構圖,設計各個零組件,同時還要給該型號的汽車取一個響亮的名字,例如“奧迪A6”。在完成設計後,就可以按照“奧迪A6”這個型號生產汽車了。

類似地,在建立一個視窗前,也必須對該類型的視窗進行設計,指定視窗的特徵。當

然,在我們設計一個視窗時,不像汽車的設計這麼複雜,因為Windows 已經為我們定義好

了一個視窗所應具有的基本屬性,我們只需要像考試時做填空題一樣,將需要我們填充的部分填寫完整,一種視窗就設計好了。(精彩的比喻)”

 

行15-行25:定義一個wndcls視窗類別對象,並填充它。見代碼注釋(遇到新概念或看不懂先跳過,以後詳細講,下同)

 

2.註冊視窗類別

在設計完汽車後,需要報經國家有關部門審批,批准後才能生產這種類型的汽車。同

樣地,設計完視窗類別(WNDCLASS)後,需要調用RegisterClass 函數對其進行註冊,注

冊成功後,才可以建立該類型的視窗。”

 

行26:註冊視窗

 

3.建立視窗——步驟3

設計好視窗類別並且將其成功註冊之後,就可以用CreateWindow 函數產生這種類型的

視窗了。”

 

行27-行39:建立視窗(遇到新概念或看不懂先跳過,以後詳細講)

 

4.顯示及更新視窗

(1)顯示視窗

視窗建立之後,我們要讓它顯示出來,這就跟汽車生產出來後要推向市場一樣。調用

函數ShowWindow 來顯示視窗。

(2)更新視窗

在調用 ShowWindow 函數之後,我們緊接著調用UpdateWindow 來重新整理視窗,就好像

我們買了新房子,需要裝修一下。”

 

行41、41:顯示及更新視窗

 

運行:

    請回顧上回“windows程式運行機制——一訊息為基礎,以事件驅動之”,程式由一系列複雜的過程建立出來顯示在螢幕上後,程式就時刻在等待訊息,然後處理訊息,這就是它“活著”。

程式是如何等待訊息的呢,又是怎樣處理的呢?請看代碼,我們就來到了while訊息迴圈

見行43-49 (只大致知道函數作用即可,現在不必深究其原理和用法)

函數GetMessage可以從訊息佇列中檢索出與此應用程式視窗有關連的訊息(一般情況,函數返回非零值讓迴圈一直進行,什麼時候函數返回零,訊息迴圈結束,我們後面講),並將訊息的具體內容存於具有MSG類型的一個變數中(即msg,MSG類型後面講),然後交由函數TranslateMessage對該訊息進行翻譯(翻譯後面講),緊接著,函數DispatchMessage將訊息發送到適當的“對象”處理。

while訊息迴圈如此往複進行,程式就能“活著”,不斷進行等待訊息、處理訊息。

 

關於這個神秘的“對象”叫做視窗過程,它是一個函數(即行50-76,行2-7是此函數的聲明),用來處理訊息。你仔細觀察你會發現這個函數的主體就是switch選擇語句,那他選擇的WM_CREATE,WM_PAINT,WM_DESTROY這又是什麼呢,或許你已經猜到了,這些就是要特意處理的訊息,如果傳進來的訊息沒有響應它的case 語段,那就一律交由default語段處理(看來它是必不可少的)。彷彿windows一切神秘的面紗已經漸漸地被我們掀開了!一個windows程式原來是這樣這樣的:它首先要被建立並顯示在案頭上,這要經過設計一個視窗類別、註冊視窗類別、建立視窗、顯示及更新視窗等過程,你想要它有什麼功能,就要在視窗過程函數switch選擇語句中特意加一case語段來處理與你這功能相關的訊息,對於不相關的訊息一率交由default語段處理。可以這樣說很多應用程式之所以不同,區別就在於其視窗過程的不同。至於while訊息迴圈,這是固定的寫法,我們只要這麼寫,具體訊息的路由都是windows系統自動完成的,我們不用操心。

死亡:

    

講到此或許讀者應該可以推斷出一點有關程式死亡的一點資訊了:視窗程序要 “死亡”肯定是要視窗函數收到某種訊息並響應,讓視窗銷毀,同時還要結束while訊息迴圈。(就先瞭解到這兒,下回再深入)

 

     這次就先講到這了,希望讀者對windows程式的“一生”和它的運行機制有那麼一點印象,能以宏觀的角度看待它,那我就能感到心滿意足了。這一回大家主要是理解,下一回我將詳細講代碼中函數的具體用法和相關細節,需要大家多記憶。好了,大家後會有期。

    

 

①  說一下大家可能不熟悉的:

      控制框是每個視窗左上方的小圖片,每個應用程式都使用它。在控製圖標上單擊滑鼠鍵會使Windows顯示系統菜單。系統功能表它提供了諸如還原、移動、大小、最小化、最大化以及關閉這樣的標準操作。

      通常客戶區佔據了視窗最大的部分(即記事本中可以寫字的地區)。這是應用程式的基本輸出地區。應當由應用程式來複雜管理客戶區。另外,應用程式可以輸出到客戶區。

 

②  補充:

     我們通常說:“某一件事發生了”和“向什麼發送某一個訊息”。比如在案頭上單擊滑鼠時,某一件事發生了,Windows首Crowdsourced Security Testing道這件事的發生,然後使用函數SendMessage向案頭發送一個訊息,證明有某件事發生了。這就是“事件驅動、訊息處理”的原理。

事件是一個動作——使用者觸發的動作。
訊息是一個資訊——傳遞給系統的資訊。這裡強調的是:
可以說“使用者觸發了一個事件”,而不能說“使用者觸發了一個訊息”。
使用者只能觸發事件,而事件只能由使用者觸發。
一個事件產生後,將被作業系統轉換為一個訊息,所以一個訊息可能是由一個事件轉換而來(或者由作業系統產生)。
一個訊息可能會產生另一個訊息,但一個訊息決不能產生一個事件——時間只能由使用者觸發。

總結(事件:訊息的來源)
事件:只能由使用者通過外設的輸入產生。
訊息:(產生訊息的來源有三個)
(1) 由作業系統產生。
(2) 由使用者觸發的事件轉換而來。
(3) 由另一個訊息產生。

 

以上援引自《事件與訊息的區別》

 

Windows的訊息可分為四種類型:
  (1)輸入訊息:對鍵盤和滑鼠輸入作反應。這類輸入訊息首先放在系統訊息佇列中,然後Windows將它們送入應用程式的訊息佇列,使訊息得到處理。
  (2)控制訊息:用來與Windows的特殊控制對象,例如,對話方塊、列表框、按鈕等進行雙向通訊。這類訊息一般不通過應用程式的訊息佇列,而是直接發送到控制對象上。
  (3)系統訊息:對程式化的事件或系統時鐘中斷作出反應。有些系統訊息,例如大部分DDE訊息(程式間進行動態資料交換時所使用的訊息)要通過Windows的系統訊息佇列。而有些系統訊息,例如視窗的建立及刪除等訊息直接送入應用程式的訊息佇列。
  (4)使用者訊息:這些訊息是程式員建立的,通常,這些訊息只從應用程式的某一部分進入到該應用程式的另一部分而被處理,不會離開應用程式。使用者訊息經常用來處理選單操作:一個使用者訊息與選單中的一選項相對應,當它在應用程式隊列中出現時被處理。

    

③   模組
  在Windows中,術語“模組”一般是指任何能被裝入記憶體中啟動並執行可執行代碼和資料的集合。更明確地講,模組指的就是一個.EXE檔案(又稱為應用程式模組),或一個動態連結程式庫(DLL — Dynamic Linking Library,又被稱為動態連結程式庫模組或DLL模組),或一個裝置驅動程式,也可能是一個程式包含的能被另一個程式存取的資料資源。模組一詞也被用於特指自包含的一段程式。例如,一個可單獨編譯的源檔案,或該源檔案被編譯器處理之後所產生的目標程式。當製作一個程式時,模組一詞用於指被串連在一起的許多模組中的某個模組。
  Windows本身由幾個相關的模組組成,Windows API函數就是在Windows啟動時裝入記憶體中的幾個動態連結程式庫模組實現的。其中的三個主要模組是USER.EXE(用於視窗管理等)、KERNEL.EXE(用於記憶體管理的多任務調度)和GDI.EXE(圖形裝置介面,用於圖形輸出等)。

 

註:如未另註明,概念解釋均來自《Windows編程基礎 --概述》一文。

文中很多內容都引自《Windows編程基礎 --概述》《深入淺出MFC》《VC++深入詳解》,在此特向這三位作者致敬!

 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.