天天看點

win32(3)--消息處理機制

Windows應用程式是消息驅動的

一、消息

消息系統對win32程式來說十分重要。一個消息,是系統定義的值,它定義了一個事件,向Windows系統發出一個通知告訴應用程式的某個事件發生了,例如滑鼠點選,鍵盤按下,視窗尺寸改變等等都會使Windows系統發送消息給應用程式

二、消息結構體MSG

struct tagMSG {

    HWND        hwnd; // 視窗句柄

   UINT        message; // 消息ID(系統指定的宏)

   WPARAM      wParam;// 附加消息

   LPARAM      lParam; // 附加消息

   DWORD       time; // 投遞到消息隊列的時間

   POINT       pt;   //投遞到到隊列時的位置

} MSG, *PMSG, NEAR *NPMSG, FAR *LPMSG;

三、消息隊列

當windows應用程式開始執行的時候,系統會為目前應用程式建立一個消息隊列用來存放消息,我們可以通過GetMessage去得到消息,然後由消息處理函數處理消息。

過程:事件–> 封裝成消息 —> 投遞到消息隊列 –> 消息響應(消息處理)

1)系統消息隊列:由系統維護、存放的是系統消息,如滑鼠鍵盤的資訊。裝置驅動(mouse, keyboard)會把操作輸入轉化成消息存在系統隊列中,然後系統會把此消息放到目标視窗所在的線程的消息隊列中等待處理。

2)線程消息隊列;存放的是目前應用程式的消息。每一個GUI線程都會維護這樣一個線程消息隊列(這個隊列隻有線上程調用GDI函數時才會建立,預設不建立)。然後線程消息隊列中的消息會被送到相應的視窗過程(WndProc)處理(視窗消息處理函數)

四、消息分類

1)系統消息

系統已定義好的消息(0~0~0x3FF)

大緻分為三類

1.視窗消息:與視窗的内部運作有關,比如建立視窗、繪制視窗、銷毀視窗等。

    2.指令消息:與處理使用者請求有關,比如點選菜單、工具欄、按鈕控件等。

    3.控件通知:這個比較靈活,其Message, wParam, lParam分别為:WM_NOTIFY, 控件ID,指向NMHDR的指針。NMHDR包含控件通知的内容, 可以任意擴充。

2)自定義消息

使用者自己定義的,作業系統不知道,使用者自行處理(0x400~0x7FFF)(也就是1024-32767),通常用WM_USER + n 的方法定義(#define WM_USER 0x400)

3)隊列消息和非隊列消息

消息的擷取和發送都在隊列中即是隊列消息,如滑鼠鍵盤消息,WM_PAINT;不進隊列直接給消息處理函數為非隊列消息,例如

WM_CREATE,WM_SIZE。

GetMessage函數:從消息隊列中擷取消息,是個堵塞函數,如果沒有得到消息一直等待,直到擷取到消息。如果有消息則從消息隊列

中擷取放入MSG中,然後從消息隊列删除該消息,正常情況傳回非0;WM_QUIT消息到來的時候傳回0;錯誤傳回-1;

是以:MSDN上這樣寫:

BOOL bRet; //#define int BOOL 大寫的BOOL 就是int,和小寫bool不同

while( (bRet = GetMessage( &msg, hWnd, 0, 0 )) != 0){

if (bRet == -1)   {

// handle the error and possibly exit

}

else {

TranslateMessage(&msg);

DispatchMessage(&msg);

}

}

PeekMessage函數:從消息隊列中擷取消息,非堵塞函數,如果沒有消息直接傳回0,有消息就傳回非0。

BOOL WINAPI PeekMessage(

_Out_    LPMSG lpMsg, //消息結構體指針

_In_opt_ HWND  hWnd, //視窗句柄,一般寫NULL

_In_     UINT  wMsgFilterMin, //最小消息ID,一般寫0

_In_     UINT  wMsgFilterMax,//最大消息ID,一般寫0,上下都是0表示不過濾

_In_     UINT  wRemoveMsg //是否要移除該消息

);

最後一個參數:

PM_NOREMOVE:執行PeekMessage後不從消息隊列删除該消息

PM_REMOVE:執行完PeekMessage後從消息隊列删除該消息

PM_NOYIELD:Prevents the system from releasing any thread that is waiting for the caller to go idle (see WaitForInputIdle).

Combine this value with either PM_NOREMOVE or PM_REMOVE.

利用PeekMessage函數,我們可以把消息循環改寫一下,可以利用這個死循環做些别的事情。

while (1){

if (PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) {

if (GetMessage(&msg, NULL, 0, 0)) {

TranslateMessage(&msg);

DispatchMessage(&msg);

}

else break;

}

//這裡可以做點其他的什麼事情

}

SendMessage和PostMessage(PostThreadMessage):

LRESULT WINAPI SendMessage(

_In_ HWND   hWnd,

_In_ UINT   Msg,

_In_ WPARAM wParam,

_In_ LPARAM lParam

);

BOOL WINAPI PostMessage(

_In_opt_ HWND   hWnd,

_In_     UINT   Msg,

_In_     WPARAM wParam,

_In_     LPARAM lParam

);

SendMessage:直接把消息送到視窗消息處理函數中處理,處理完了才傳回,也就是阻塞函數。(是不是進消息隊列要看情況而定)

PostMessage:把消息放到指定視窗所在的線程消息隊列中後立即傳回。

PostThreadMessage:把消息放到指定線程的消息隊列中後立即傳回。

兩者的差別:

1、PostMessage 是異步的,SendMessage 是同步的。

PostMessage 隻把消息放到隊列,不管消息是不是被處理就傳回,消息可能不被處理;SendMessage等待消息被處理完了才傳回,

如果消息不被處理,發送消息的線程将一直處于阻塞狀态,等待消息的傳回。

2、同一個線程内:

SendMessage 發送消息時,由USER32.DLL子產品調用目标視窗的消息處理程式,并将結果傳回,SendMessage 在同一個線程裡面發

送消息不進入線程消息隊列;PostMessage 發送的消息要先放到消息隊列,然後通過消息循環分派到目标視窗(DispatchMessage)。

3、不同線程:

SendMessage 發送消息到目标線程的消息隊列,然後發送消息的線程在USER32.DLL子產品内監視和等待消息的處理結果,直到目标視窗

的才處理傳回。PostMessge()到别的線程的時候最好使用PostThreadMessage()代替。PostMessage()的HWND 參數可以為NULL,相

當于PostThreadMessage() + GetCrrentThreadId。

4、系統處理消息:

使用PostMessage,SendNotifyMessage,SendMessageCallback等異步函數發送系統消息時,參數不可以使用指針,因為發送者不等

待消息的處理就傳回,接收者還沒有處理,指針就有可能被釋放了,或則内容變化了。

TranslateMessage(轉換消息):

用來把虛拟鍵消息轉換為字元消息。由于Windows對所有鍵盤編碼都是采用虛拟鍵的定義,這樣當按鍵按下時,并不是字元消息,需要

鍵盤映射轉換為字元的消息。轉換後的字元消息被投遞到調用線程的消息隊列中,當下一次調用GetMessage函數時被取出。

TranslateMessage(&msg){

  if(沒有鍵盤消息)

return ;

  if(是否是可見字元){

  if(lock鍵盤是否按下){

    Postmessage(鍵盤按下的大寫字母消息);

}

else{

Postmessage(鍵盤按下的小寫字母消息);

}

}

}

DispatchMessage(派發消息):

把消息發送到視窗的消息處理函數。

零碎知識:在Window視窗程式中加一個DOS視窗。

一種方法:

//該代碼寫在所有的.h後面,可以讓視窗程式帶個dos視窗,友善調試

//注意 UNICODE的啟動函數是 wWinMainCRTStartup,MBCS是WinMainCRTStartup(沒有w)

#ifdef UNICODE

#pragma comment(linker,”/subsystem:\”console\” /entry:\”wWinMainCRTStartup\””)

#else

#pragma comment(linker,”/subsystem:\”console\” /entry:\”WinMainCRTStartup\””)

#endif // UNICODE

第二種方法:

AllocConsole();

HANDLE g_hand = GetStdHandle(STD_OUTPUT_HANDLE);

WriteConsole(g_hand, _T(“顯示的内容”),_tcslen(_T(“顯示的内容”)), NULL, NULL);

小練習:做一個視窗,要動态光标,點選滑鼠左鍵換一個圖示

#include <Windows.h>
#include <tchar.h>

HCURSOR MCur[2];
HINSTANCE HInstance;

//5.消息處理
LRESULT WinProc(HWND hWnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
	switch (msg)
	{
	case WM_CREATE:
		MCur[0] = (HCURSOR)LoadImage(HInstance, _T("ani1.ani"), IMAGE_CURSOR, 0, 0, LR_LOADFROMFILE);
		MCur[1] = (HCURSOR)LoadImage(HInstance, _T("ani2.ani"), IMAGE_CURSOR, 0, 0, LR_LOADFROMFILE);
		break;
	
	case WM_LBUTTONDOWN: {
		int i = 0;
		i = (++i) % 2;
		SetCursor(MCur[i]);
		break; 
	}
		
	case WM_DESTROY: 
		PostQuitMessage(0);
		break;
	
	}
	return DefWindowProc(hWnd, msg, wparam, lparam);
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE phInstance,
	LPSTR lpCmdLine,
	int nCmdShow)
{
	HInstance = hInstance;
	//1.定義視窗對象
	WNDCLASS wc; // EX
	wc.cbClsExtra = NULL;
	wc.cbWndExtra = NULL; 
	wc.hInstance = hInstance; 
							
	wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); 
	wc.hCursor = NULL;
	wc.hIcon = NULL; 

	wc.lpfnWndProc = (WNDPROC)WinProc; 
	wc.lpszClassName = _T("WA");
	wc.lpszMenuName = NULL;
	wc.style = CS_HREDRAW;

						  
	if (!RegisterClass(&wc))
	{
		MessageBox(NULL, _T("ERROR"), _T("ROORO"), MB_OK);
		return 0;
	}
	
	HWND hWnd = CreateWindow(_T("WA"), _T("啦啦啦"), WS_OVERLAPPEDWINDOW,
		200, 200, 300, 400, NULL, NULL, hInstance, NULL);
	if (!hWnd)
	{
		MessageBox(NULL, _T("ERROR"), _T("ROORO"), MB_OK);
		return 0;
	}

	
	ShowWindow(hWnd, SW_SHOW);
	UpdateWindow(hWnd);


	
	MSG msg = { 0 };
	while (GetMessage(&msg, NULL, 0, 0))
	{
		
		TranslateMessage(&msg);
		
		DispatchMessage(&msg);
	}
	
	UnregisterClass(_T("WA"), hInstance);
	return 0;
}