1. 程式人生 > >Windows程式的視窗和訊息 -- 一個Windows程式從生到死

Windows程式的視窗和訊息 -- 一個Windows程式從生到死

注:以下內容為學習筆記,多數是從書本、資料中得來,只為加深印象,及日後參考。然而本人表達能力較差,寫的不好。因非翻譯、非轉載,只好選原創,但多數乃摘抄,實為慚愧。但若能幫助一二訪客,幸甚!

1.一個完整的Windows程式

/*-----------------------------------------------------------------------------------
	HelloWin.cpp -- Displays "Hello, Windows 7!" in client area
 *----------------------------------------------------------------------------------*/

#include <windows.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
	static TCHAR	szAppName[] = TEXT("HelloWin32");
	HWND			hwnd;
	MSG				msg;
	WNDCLASS		wndclass;

	wndclass.style			= CS_HREDRAW | CS_VREDRAW;
	wndclass.lpfnWndProc	= WndProc;
	wndclass.cbClsExtra		= 0;
	wndclass.cbWndExtra		= 0;
	wndclass.hInstance		= hInstance;
	wndclass.hIcon			= LoadIcon(NULL, IDI_APPLICATION);
	wndclass.hCursor		= LoadCursor(NULL, IDC_ARROW);
	wndclass.hbrBackground	= (HBRUSH)GetStockObject(WHITE_BRUSH);
	wndclass.lpszMenuName	= NULL;
	wndclass.lpszClassName	= szAppName;

	if (!RegisterClass(&wndclass))
	{
		MessageBox(NULL, TEXT("This program requires windows NT!"), szAppName, MB_ICONERROR);
		return 0;
	}

	hwnd = CreateWindow(szAppName,					// window class name
						TEXT("The Hello Program"),	// window caption
						WS_OVERLAPPEDWINDOW,		// window style
						CW_USEDEFAULT,				// initial x position
						CW_USEDEFAULT,				// initial y position
						CW_USEDEFAULT,				// initial x size
						CW_USEDEFAULT,				// initial y size
						NULL,						// parent window handle
						NULL,						// window menu handle
						hInstance,					// program instance handle
						NULL);						// creation parameters

	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;
	RECT		rect;

	switch (message)
	{
	case WM_CREATE:
		PlaySound(TEXT("hellowin32.wav"), NULL, SND_FILENAME | SND_ASYNC);
		return 0;

	case WM_PAINT:
		hdc = BeginPaint(hwnd, &ps);

		GetClientRect(hwnd, &rect);
		DrawText(hdc, TEXT("Hello Windows 7!"), -1, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER);

		EndPaint(hwnd, &ps);
		return 0;

	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	}

	return DefWindowProc(hwnd, message, wParam, lParam);
}

2.註冊視窗類

要建立視窗,首先需要註冊一個視窗類,而視窗類又需要視窗過程來處理視窗訊息。

視窗總是基於視窗類來建立的。視窗類確定了處理視窗訊息的視窗過程。多個視窗可以同時基於某一視窗類來建立。

typedef struct {
    UINT style;
    WNDPROC lpfnWndProc;
    int cbClsExtra;
    int cbWndExtra;
    HINSTANCE hInstance;
    HICON hIcon;
    HCURSOR hCursor;
    HBRUSH hbrBackground;
    LPCTSTR lpszMenuName;
    LPCTSTR lpszClassName;
} WNDCLASS, *PWNDCLASS;
ATOM RegisterClass(CONST WNDCLASS *lpWndClass);

過程:定義一個WNDCLASS結構並對它的10個子段進行初始化,並呼叫RegisterClass來完成該視窗類的註冊。

3.視窗的建立

通過呼叫CreateWindow來完成。

HWND CreateWindow(LPCTSTR lpClassName,	// 視窗類名稱
    LPCTSTR lpWindowName,		// 視窗標題
    DWORD dwStyle,			// 視窗風格,或稱視窗格式
    int x,				// 初始x座標
    int y,				// 初始y座標
    int nWidth,				// 初始x方向尺寸
    int nHeight,			// 初始y方向尺寸
    HWND hWndParent,			// 父視窗控制代碼
    HMENU hMenu,			// 視窗選單控制代碼
    HINSTANCE hInstance,		// 程式例項控制代碼
    LPVOID lpParam			// 建立引數
);


控制代碼:Windows中,控制代碼的使用非常頻繁。控制代碼本質上是引用某個物件的數值。Windows中的控制代碼非常類似於傳統的C或MS-DOS程式中使用的檔案控制代碼。

如果新建的視窗為頂級視窗,則父視窗控制代碼為NULL。

CreateWindow的返回值為一個指向所建立視窗的控制代碼。

4.視窗的顯示

CreateWindow呼叫返回時,視窗已在Windows內部被建立。這表示Windows已經分配了一塊記憶體來儲存指定的視窗資訊以及一些其他資訊。

要將視窗顯示在螢幕上,還需要:

1)ShowWindow(hwnd, iCmdShow);

用於將視窗顯示在螢幕中。如果第二個引數為SW_SHOWNORMAL,則該視窗的客戶區將被在視窗類中所指定的背景畫刷擦除。

2)UpdateWindow(hwnd);

通過向視窗過程傳送一條WM_PAINT訊息,使視窗客戶區重繪。

5.訊息迴圈

在UpdateWindow被呼叫之後,新建視窗在螢幕中便完全可見了。此時,該程式必須能夠接收來自使用者的鍵盤輸入和滑鼠輸入。Windows為當前在其中執行的每一個Windows程式都維護一個訊息佇列。當輸入事件發生後,Windows會自動將這些事件轉換為訊息,並將其放置在應用程式的訊息佇列中。

應用程式通過執行一段名為“訊息迴圈”的程式碼段來從訊息佇列中獲取訊息:

while (GetMessage(&msg, NULL, 0, 0))
{
	TranslateMessage(&msg);
	DispatchMessage(&msg);
}

GetMessage用於從訊息佇列中對訊息進行檢索。若訊息為WM_QUIT,則返回0值,while迴圈退出。

TranslateMessage將msg結構返還給Windows以進行某些鍵盤訊息的轉換。

DispatchMessage將msg再次返回給Windows,Windows會將這條訊息傳送給合適的視窗過程來處理。即Windows呼叫了視窗過程。

6.視窗過程

前面的都是常規步驟。真正有意義的事情發生在視窗過程中。正是視窗過程決定了視窗客戶區的顯示內容以及視窗如何對使用者的輸入做出響應。

應用程式通常並不直接對視窗過程進行呼叫。視窗過程幾乎總是由Windows自身呼叫的。應用程式如果希望呼叫自身的視窗過程,則可通過呼叫函式SendMessage來實現。

7.訊息的處理

視窗過程所接收的每一條訊息都由一個數字來標示,即視窗過程的message引數。

通常使用switch-case結構來確定視窗過程所收到的訊息的型別以及相應的處理方法。當視窗過程對訊息進行處理後,返回0。

所有視窗過程不進行處理的訊息必須傳給名稱為DefWindowProc的函式。該函式的返回值必須從視窗過程返回。若不如此,其他的正常行為(如結束程式)將無法進行。

8.WM_CREATE

視窗過程所接收到的第一條訊息為WM_CREATE。當Windows在WinMain函式中處理CreateWindow時,WndProc將收到該訊息。WndProc處理完WM_CREATE訊息後,將控制權返回給Windows。然後,Windows從CreateWindow呼叫返回,繼續執行WinMain中的其他程式碼。

通常情況下,視窗過程會在處理WM_CREATE訊息期間對視窗進行一次性的初始化。

9.WM_PAINT

WndProc所處理的第二條訊息是WM_PAINT。當視窗的客戶區的部分或全部“無效”且必須“更新”時,應用程式將得到此通知。這也就意味著視窗必須被“重繪”。

視窗尺寸發生變化、最小化然後恢復到原先的尺寸、拖動視窗發生重疊時會標記視窗無效,視窗過程會收到WM_PAINT訊息,並對視窗的內容進行重繪。

對WM_PAINT訊息的處理幾乎總是從呼叫BeginPaint開始,以呼叫EndPaint結束。

BeginPaint呼叫期間,如果客戶區的背景尚未被擦除,則Windows會對其使用WNDCLASS中的hbrBackground指定的畫刷進行擦除。BeginPaint的呼叫將使整個客戶區有效,並返回一個“裝置環境控制代碼”。裝置環境是指物理輸出裝置(如視訊顯示器)及其裝置驅動程式。我們需要裝置環境控制代碼以在視窗客戶區顯示文字和影象,該控制代碼無法在客戶區以外的區域進行繪製。EndPaint用於釋放裝置環境控制代碼,以使其無效。

10.WM_DESTROY

當用戶點選關閉按鈕或其他關閉程式手段時,發出WM_DESTROY訊息。

標準響應方式:PostQuitMessage。

該函式是將一個WM_QUIT訊息插入到程式的訊息佇列中。

11.何時呼叫WndProc

在下面的場景下,WndProc會被Windows系統呼叫:

1)新建視窗

2)視窗被最終銷燬時

3)視窗尺寸發生變化或被移動或被最小化時

4)使用者用滑鼠在視窗中執行單擊或雙擊操作時

5)使用者從鍵盤輸入字元時

6)使用者從選單中選擇某個選單項時

7)使用者用滑鼠或單擊滾動條時

8)視窗的客戶區需要重繪時

12.佇列訊息和非佇列訊息

訊息既可以是“佇列訊息”,也可以是“非佇列訊息”。佇列訊息是指那些由Windows放入程式的訊息佇列中的訊息。非佇列訊息則是由Windows對視窗過程的直接呼叫而產生的。

佇列訊息一般由使用者的輸入產生,如按鍵、滑鼠訊息等;非佇列訊息則包括佇列訊息以外的其他所有訊息,通常由呼叫特定的Windows函式引起。uCreateWindow時,傳送的WM_CREATE訊息。

注:以下內容來自侯捷先生所著《深入淺出MFC》

13.RegisterClass與CreateWindow


一開始,Windows 程式必須做些初始化工作,為的是產生應用程式的工作舞臺:視窗。這沒有什麼困難,因為API 函式CreateWindow 完全包辦了整個巨大的工程。但是視窗
產生之前,其屬性必須先設定好。所謂屬性包括視窗的「外貌」和「行為」,一個視窗的邊框、顏色、標題、位置等等就是其外貌,而視窗接收訊息後的反應就是其行為(具
體地說就是指視窗函式本身)。程式必須在產生視窗之前先利用API 函式RegisterClass設定屬性(我們稱此動作為註冊視窗類別)。RegisterClass 需要一個大型資料結構
WNDCLASS 做為引數,CreateWindow 則另需要11 個引數。

14.Windows程式的生與死---視窗的生命週期


1)程式初始化過程中呼叫CreateWindow,為程式建立了一個視窗,做為程式的螢幕舞臺。CreateWindow 產生視窗之後會送出WM_CREATE 直接給視窗函式,後者於是可以在此時機做些初始化動作(例如配置記憶體、開檔案、讀初始資料...)。
2)程式活著的過程中,不斷以GetMessage 從訊息貯列中抓取訊息。如果這個訊息是WM_QUIT,GetMessage 會傳回0 而結束while 迴圈,進而結束整個程式。
3)DispatchMessage 透過Windows USER 模組的協助與監督,把訊息分派至視窗函式。訊息將在該處被判別並處理。
4)程式不斷進行2. 和3. 的動作。
5)當使用者按下系統選單中的Close 命令項,系統送出WM_CLOSE。通常程式的視窗函式不欄截此訊息,於是DefWindowProc 處理它。
6)DefWindowProc 收到WM_CLOSE 後, 呼叫DestroyWindow 把視窗清除。DestroyWindow 本身又會送出WM_DESTROY。
7)程式對WM_DESTROY 的標準反應是呼叫PostQuitMessage。
8)PostQuitMessage 沒什麼其它動作,就只送出WM_QUIT 訊息,準備讓訊息迴圈中的GetMessage 取得,如步驟2,結束訊息迴圈。

15.OnIdle

所謂空閒時間(idle time),是指「系統中沒有任何訊息等待處理」的時間。舉個例子,沒有任何程式使用定時器(timer,它會定時送來WM_TIMER),使用者也沒有碰觸鍵盤和滑鼠或任何外圍,那麼,系統就處於所謂的空閒時間。
空閒時間常常發生。不要認為你移動滑鼠時產生一大堆的WM_MOUSEMOVE,事實上夾雜在每一個WM_MOUSEMOVE 之間就可能存在許多空閒時間。畢竟,計算機速度超乎想像。
背景工作最適宜在空閒時間完成。傳統的SDK 程式如果要處理空閒時間,可以以下列迴圈取代WinMain 中傳統的訊息迴圈:

while (TRUE) 
{
	if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) 
	{
		if (msg.message == WM_QUIT)
			break;
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
	else 
	{
		OnIdle();
	}
}

原因是PeekMessage 和GetMessage 的性質不同。它們都是到訊息佇列中抓訊息,如果抓不到,程式的主執行執行緒(primary thread,是一個UI 執行執行緒)會被作業系統虛懸住。當作業系統再次回來照顧此一執行執行緒,而發現訊息佇列中仍然是空的,這時候兩個API函式的行為就有不同了:
  GetMessage 會過門不入,於是作業系統再去照顧其它人。
  PeekMessage 會取回控制權,使程式得以執行一段時間。於是上述訊息迴圈進入OnIdle 函式中。

16.一個程序的誕生與死亡

1)shell 呼叫CreateProcess 啟用App.exe。
2)系統產生一個「程序核心物件」,計數值為1。
3)系統為此程序建立一個4GB 地址空間。
4)載入器將必要的碼載入到上述地址空間中,包括App.exe 的程式、資料,以及所需的動態聯結函式庫(DLLs)。載入器如何知道要載入哪些DLLs 呢?它們被記錄在可執行檔案(PE 檔案格式)的.idata section 中。
5)系統為此程序建立一個執行執行緒,稱為主執行執行緒(primary thread)。執行執行緒才是CPU 時間的分配物件。
6)系統呼叫C runtime 函式庫的Startup code。
7)Startup code 呼叫App 程式的WinMain 函式。
8)App 程式開始運作。
9)使用者關閉App 主視窗,使WinMain 中的訊息迴圈結束掉,於是WinMain 結束。
10)回到Startup code。
11)回到系統,系統呼叫ExitProcess 結束程序。

17.一個執行執行緒的誕生與死亡

1)配置「執行執行緒物件」,其handle 將成為CreateThread 的傳回值。
2)設定計數值為1。
3)配置執行執行緒的context。
4)保留執行執行緒的堆疊。
5)將context 中的堆疊指標快取器(SS)和指令指標快取器(IP)設定妥當。

18.Windows程式與作業系統之間的關係


Windows 程式的進行系依靠外部發生的事件來驅動。換句話說,程式不斷等待(利用一個while 迴路),等待任何可能的輸入,然後做判斷,然後再做適當的處理。上述的「輸入」是由作業系統捕捉到之後,以訊息形式(一種資料結構)進入程式之中。作業系統如何捕捉外圍裝置(如鍵盤和滑鼠)所發生的事件呢?噢,USER 模組掌管各個外圍的驅動程式,它們各有偵測迴路。
如果把應用程式獲得的各種「輸入」分類,可以分為由硬體裝置所產生的訊息(如滑鼠移動或鍵盤被按下),放在系統佇列(system queue)中,以及由Windows 系統或其它
Windows 程式傳送過來的訊息,放在程式佇列(application queue)中。以應用程式的眼光來看,訊息就是訊息,來自哪裡或放在哪裡其實並沒有太大區別,反正程式呼叫
GetMessage API 就取得一個訊息,程式的生命靠它來推動。所有的GUI 系統,包括UNIX的X Window 以及OS/2 的Presentation Manager,都像這樣,是以訊息為基礎的事件驅動系統。

接受並處理訊息的主角就是視窗。每一個視窗都應該有一個函式負責處理訊息,程式設計師必須負責設計這個所謂的「視窗函式」(window procedure,或稱為window function)。
如果視窗獲得一個訊息,這個視窗函式必須判斷訊息的類別,決定處理的方式。以上就是Windows 程式設計最重要的觀念。至於視窗的產生與顯示,十分簡單,有專門的API 函式負責。稍後我們就會看到Windows 程式如何把這訊息的取得、分派、處理動作表現出來。

相關推薦

Windows程式視窗訊息 -- 一個Windows程式生到死

注:以下內容為學習筆記,多數是從書本、資料中得來,只為加深印象,及日後參考。然而本人表達能力較差,寫的不好。因非翻譯、非轉載,只好選原創,但多數乃摘抄,實為慚愧。但若能幫助一二訪客,幸甚! 1.一個完整的Windows程式 /*---------------------

Windows下用Eclipse建立一個spark程式三步曲(Java版)

作者:翁鬆秀 用Eclipse建立一個spark程式三步曲(Java版) 用Eclipse建立一個spark程式三步曲(Java版) Step1:建立Maven工程

【VS開發】【OpenGL開發】OpenGL---Windows下配置與第一個OpenGL程式

面記錄一下Windows下配置OpenGL與我的第一個OpenGL程式。 第一步:選擇一個編譯環境 現在Windows系統的主流編譯環境有Visual Studio,Broland C++ Builder,Dev-C++等,它們都是支援OpenGL的。但這裡我選擇的是V

Windows程式設計學習筆記(三)——視窗訊息

MessageBox函式會建立一個‘視窗’。在Windows中,一個視窗就是螢幕上一個矩形區域,它接收使用者的輸入並以文字或圖形的格式顯示輸出內容。MessageBox函式建立一個視窗,但只是一個功能有

Win32 程式開發:建立一個應用程式視窗

1)簡單介紹建立應用程式的步驟 1.設計一個視窗類 2.註冊這個視窗類 3.建立應用程式視窗 4.更新顯示視窗 5.應用程式訊息迴圈 2)下面根據這個步驟進行建立一個應用程式視窗吧 /* 標頭檔案 */ #include <windows.h>

Android之JNI① AS3.0以下DNK下載配置一個JNI程式

一、JNI介紹 JNI(Java Native Interface):一個協議,這個協議用來溝通java程式碼和外部的原生代碼(c/c++), 外部的c/c++程式碼也可以呼叫java程式碼。 1.1 C語言的優勢: ①效率上 C/C++是本地語言,比java更高效;

Windows API 程式設計之建立一個windows視窗

直接上程式碼。 /************************************************************************************** * 問題:使用windows API函式建立一個window

Python札記(一)-開發環境搭建(ForMac)一個Python程式

 今天的內容大致如下: 自己先看一下Python的功效,多多少少去了解一下。我們必須安裝一個Python環境在自己的PC上,用來解釋自己書寫的程式碼,本來Mac是內建的,但是是Python2.7.10(隨著新電腦的出售和系統更新,預裝版本可能會有差異,在終端輸入Python

.NET Core3.1總體預覽一個Core程式的建立

小夥伴們大家好!歡迎閱讀本貼,這裡是常哥說程式設計的專欄,.NetCore已經出來一段時間了,很多小夥伴可能也開始了學習,但是.NetCore畢竟在學習上和我們常用的.NET Framework還是有很大差別的,為了幫助大家能儘快的進入.NetCore的開發,常哥把在喜科堂講解的關於Core的內容都做了新的整

微信小程式開發——開啟另一個程式

微信小程式開啟另一個小程式,有兩種方法:1.超連結;2.點選按鈕。 全域性配置: 跳轉到其他小程式,需要在當前小程式全域性配置中配置需要跳轉的小程式列表,程式碼如下: App.json { ... "navigateToMiniProgramAppIdList": [ "w

一個程式設計師對另一個程式設計師的忠告

自己關於所從事工作的一些看法,希望對你有用。 為什麼要選擇軟體網際網路行業 從巨集觀上來看,軟體網際網路工作者算得上是這個時代的弄潮兒,站在潮流的前沿,致力於為使用者創造更加時尚便捷優質的生活。在當今這個移動網際網路的浪潮之中,你可以感受到網際網路正在改變人們的生活方式和

科班出身程式設計師培訓出來的程式設計師區別在哪?

科班出身只是代表你要從事的職業和你的專業是一致的,代表著你具備了得天獨厚的優勢,至於是不是優勢看你上班好不好學,平時逃課沒,上班是認真聽還是玩手機。一般而言科班出來的理論性比較強,實踐能力稍微差點,畢竟學校主要引導的思路的學習,有些人覺得學校就應該上來實踐化的學習和社會接軌,那不就成職業學院

windows 使用者登入登出監控|windows session 狀態改變監控

不知道大家注意到沒有,Windows XP新增加了一個快速切換使用者的功能。它可以讓您在不登出的情況下在使用者之間進行切換,而且每個使用者有自己獨立的配置檔案和桌面。也就是說當您切換到另外 一個使用者(即用另外一個賬號登陸)時,以前的那個使用者執行的所有程式都還是在執行的

嵌入式偵錯程式原理各類偵錯程式集錦

        工欲善其事,必先善其器。偵錯程式在嵌入式開發除錯中的重要性不言而喻,單步、斷點和監察的效率遠高於串列埠列印。但是,偵錯程式對於一般開發人員往往是一個黑匣子。今天我們就來談談偵錯程式的原理

網際網路程式設計師外包公司的程式設計師有什麼區別?

網際網路的到來就註定會有外包公司的誕生,起初外包公司給一些不願意花高代價招程式設計師的創業型小企

程式設計師之路——一個程式設計師對剛上大學的學弟學妹的忠告

http://blog.csdn.net/immiao/article/details/44873921 始終認為,對一個初學者來說,IT界的技術風潮是不可追趕。 我時常看見自己的DDMM們把課本扔了,去買些價格不菲的諸如C#, VB.Net 這樣的大部頭,這讓我感

漫談程式設計師系列:一個程式設計師的2014年終總結

人生天地之間,若白駒過隙,忽然而已。驀然回首,頭上似霜雪,臉上似山川。我的 2014 啊,你就這麼毫無眷戀地離我而去了。既然往事已不可追,未來尚未到來,在這年末歲首的間隙,就讓我來回憶一下餘煙尚存的 2014 吧。 2014 年,對我而言,發生了很多事,不管怎樣,我

程式設計師系列:一個程式設計師的多年總結

展望未來,總結過去10年的程式設計師生涯,給程式設計師小弟弟小妹妹們的一些總結性忠告 走過的路,回憶起來是那麼曲折,把自己的一些心得體會分享給程式設計師兄弟姐妹們,雖然時代在變化,但是很可能你也會走我已經做過的10年的路程,有些心得體會你可以借鑑一下,覺得說得有道理的你就接納

微信小程式:如何建立一個程式頁面?

要在微信小程式上開發一個功能模組,通常需要先建立一個新的頁面,方法很簡單,示例如下: 例如,需要新建一個名稱為one的頁面,只需要在專案的根目錄下的app.json檔案下的pages陣列中新建一行:"pages/one/one"。 { "pages":[ "pa

Windows程式訊息機制(一):視窗程式的建立

Windows視窗程式的實現 上面介紹了Windows下的訊息機制,系統傳送訊息到程式,程式接收到訊息後的處理統稱為視窗過程。 要實現視窗過程當然需要先建立一個視窗程式了。視窗程式的建立很簡單,主要分為以下幾個步驟: 註冊視窗類建立視窗及顯示視窗建立訊