本文說(shuō)明兩個(gè)問(wèn)題:
1.windows的消息處理機(jī)制;
2.怎么往SetTimer的回調(diào)函數(shù)傳遞參數(shù)。
首先看第一個(gè)問(wèn)題,我們都知道windows是消 息驅(qū)動(dòng)的,windows呈現(xiàn)給用戶的任何可以看到聽(tīng)到的東西幾乎都是消息驅(qū)動(dòng)的,在底層windows為每個(gè)線程準(zhǔn)備了一個(gè)消息隊(duì)列,如果用戶線程注冊(cè) 了某個(gè)消息,那么在適當(dāng)?shù)臅r(shí)候windows就會(huì)將消息投遞到該線程的消息隊(duì)列中,然后由該線程取出隊(duì)列中的消息,然后處理之,這個(gè)過(guò)程有兩個(gè)參與者,一 個(gè)是windows系統(tǒng),它主要負(fù)責(zé)投遞消息,收不收是用戶線程的事,另一個(gè)就是用戶線程,它主要負(fù)責(zé)取出消息并處理消息,即使用戶線程因?yàn)樗呋蛘吒揪蜎](méi)有設(shè)定消息循環(huán),系統(tǒng)還是會(huì)投遞的,系統(tǒng)和用戶線程的消息接口就是消息隊(duì)列,這就在用戶和系統(tǒng)之間關(guān)于消息解除了耦合,在用戶線程處理消息的時(shí)候,其實(shí)還有一個(gè)消息隊(duì)列,因?yàn)橐粋€(gè)線程不一定只接收一種消息而且不一定馬上就能處理完并返回,這個(gè)消息隊(duì)列我們把它叫做消息分發(fā)隊(duì)列或者簡(jiǎn)稱分發(fā)隊(duì)列用來(lái)與系統(tǒng)的消息隊(duì)列區(qū)分,注意分發(fā)隊(duì)列里面的消息都是已經(jīng)格式化后的消息,分發(fā)給誰(shuí)呢?當(dāng)然是分發(fā)給消息的回調(diào)函數(shù)了,對(duì)于有窗口的就是先分發(fā)給窗口過(guò)程,然后 由窗口過(guò)程分發(fā)給具體的處理函數(shù)。
下面我們來(lái)通過(guò)一個(gè)例子說(shuō)明一下,用vs2005或VC建立一個(gè)Win32工程,然后看自動(dòng)生成的代碼:
int APIENTRY _tWinMain(HINSTANCE hInstance,

HINSTANCE hPrevInstance,

LPTSTR lpCmdLine,

int nCmdShow)


{
// 主消息循環(huán):
while (GetMessage(&msg, NULL, 0, 0))

{
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))

{
TranslateMessage(&msg);
DispatchMessage(&msg);//msg中按照消息號(hào)識(shí)別
}
}
return (int) msg.wParam;
}
以上就是消息循環(huán),該線程循環(huán)接收消息,然后DispatchMessage消息,Dispatch到窗口過(guò)程:
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)


{
int wmId, wmEvent;
PAINTSTRUCT ps;
HDC hdc;

switch (message)//message就是消息號(hào)

{
case WM_COMMAND:


default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
以上實(shí)際上就是windows消息機(jī)制的全景,對(duì)于windows的timer當(dāng)然也要套用上面的模式了,在SetTimer調(diào)用后,實(shí)際上就注冊(cè)了WM_TIMER消息,以下是函數(shù)定義:
UINT_PTR SetTimer(

HWND hWnd,

UINT_PTR nIDEvent,

UINT uElapse,

TIMERPROC lpTimerFunc

);
lpTimerFunc就是回調(diào)函數(shù),其形式為:
VOID CALLBACK TimerProc(

HWND hwnd,

UINT uMsg,

UINT_PTR idEvent,

DWORD dwTime

);
SetTimer的參數(shù)uElapse就是時(shí)間間隔,比如設(shè)置為1000即1秒,現(xiàn)在有了一個(gè)問(wèn)題,請(qǐng)看下列代碼:
VOID CALLBACK TimerFunc(HWND hwnd,UINT uMsg,UINT_PTR idEvent,DWORD dwTime)



{

Sleep(5000);

}

DWORD CALLBACK AutoBakup( PVOID lpParam )


{
MSG msg;

UINT id=SetTimer(NULL,1,1000,TimerFunc);

BOOL bRet;

while( ((bRet = GetMessage(&msg,NULL,0,0))!= 0) )

{
if(bRet==-1)

{
break;
}
else

{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}

KillTimer(NULL,id);
return 0;
}
竟然在timer的處理函數(shù)中睡眠了,那么SetTimer時(shí)的1000毫秒觸發(fā)一次timer回調(diào)還會(huì)進(jìn)行嗎?其實(shí)不會(huì)進(jìn)行了,本質(zhì)上那個(gè)1000毫秒就 不是說(shuō)觸發(fā)回調(diào)函數(shù)的間隔,而是產(chǎn)生WM_TIMER消息的間隔,因?yàn)榛卣{(diào)函數(shù)中睡眠了,所以也就阻塞了本線程,這個(gè)線程就不再往前走了,但是底層的 WM_TIMER消息也會(huì)因此而不再投遞嗎?不會(huì),消息的投遞其實(shí)不是本線程進(jìn)行的,而是系統(tǒng)進(jìn)行的,本線程已經(jīng)睡眠了,但是系統(tǒng)卻沒(méi)有睡眠,它會(huì)繼續(xù)往該線程的消息隊(duì)列投遞WM_TIMER消息,只不過(guò)這些消息不再由該線程的GetMessage取出了,因?yàn)樗吡耍ㄈ绻麑?duì)回調(diào)函數(shù)是否和 GetMessage是否為統(tǒng)一線程有疑義,那么用GetCurrentThreadId()檢測(cè)一下就好),消息全部堆積在隊(duì)列里面,然后等待睡眠結(jié)束后再一個(gè)一個(gè)處理。好吧,這個(gè)問(wèn)題解決了,下一個(gè)問(wèn)題又來(lái)了,試著將回調(diào)函數(shù)改為下面的:
VOID CALLBACK TimerFunc(HWND hwnd,UINT uMsg,UINT_PTR idEvent,DWORD dwTime)


{
MessageBoxA(NULL,"zhaoya","msg",MB_OK);
Sleep(5000);
}
發(fā)現(xiàn)完全按照SetTimer時(shí)設(shè)置的那樣,1秒彈出一個(gè)消息框,一個(gè)一個(gè)出來(lái),Sleep好像根本就沒(méi)有執(zhí)行,線程根本沒(méi)有睡眠阻塞,我把 MessageBoxA(NULL,"","",MB_OK)換成printf就不行了,線程立即執(zhí)行Sleep,這是為何?關(guān)鍵就在 MessageBoxA上,它實(shí)際上是一個(gè)模態(tài)對(duì)話框,既然是對(duì)話框它就有消息循環(huán),它并沒(méi)有開(kāi)獨(dú)立的線程,因此可以肯定還是原來(lái)的線程,原來(lái) MessageBoxA的內(nèi)部實(shí)現(xiàn)了GetMessage-->TranslateMessage-->DispatchMessage的循環(huán),由于還是原來(lái)的線程,所以它GetMessage將還包括WM_TIMER,另外還有消息框自己的一些關(guān)于界面的消息,比如WM_PAINT,消息框 的出現(xiàn)只是在系統(tǒng)中又注冊(cè)了一些需要的消息,就是消息框的關(guān)于界面事件的消息。WM_TIMER回調(diào)函數(shù)在那,于是調(diào)用之,于是又是一個(gè)消息框出來(lái)了,但 是一旦你點(diǎn)擊最上面的OK鍵,那么程序立即向下進(jìn)行,進(jìn)入Sleep,開(kāi)始睡眠,所有的消息框好像死掉一般,因?yàn)榫鸵粋€(gè)線程,它睡眠了,消息循環(huán)不再進(jìn)行,當(dāng)然所有消息框的消息循環(huán)也不再進(jìn)行,什么WM_PANIT之類的消息都將阻塞,于是消息框們都和死了一樣。
通過(guò)以上論述,我想關(guān)于windows消息大致已經(jīng)說(shuō)清了,下面解決第二個(gè)問(wèn)題,如何向SetTimer的回調(diào)函數(shù)傳遞自定義參數(shù)。再看TimerProc:
VOID CALLBACK TimerPro(HWND hwnd,UINT uMsg,UINT_PTR idEvent,DWORD dwTime);
看看MSDN關(guān)于第三個(gè)參數(shù)idEvent的解釋,就是Timer的id,我們可以用SetTimer的返回值來(lái)作為自定義參數(shù)的指針,然后在 TimerProc回調(diào)函數(shù)中通過(guò)idEvent取出,強(qiáng)制轉(zhuǎn)換為自己定義的類型,可是SetTimer的返回值并不是我們所能左右的啊,那么還是要從 MSG結(jié)構(gòu)下手了:

typedef struct tagMSG
{ // msg

HWND hwnd;

UINT message;

WPARAM wParam;

LPARAM lParam;

DWORD time;

POINT pt;

} MSG;
看看wParam和lParam,MSDN上說(shuō)是額外參數(shù),我們只需要重新設(shè)置值就可以了,于是我們要取wParam和lParam這兩個(gè)參數(shù)與 TimerProc形參的交集,這個(gè)交集就是我們可以自定義的參數(shù),經(jīng)過(guò)測(cè)試,發(fā)現(xiàn)wParam其實(shí)就是SetTimer返回的id,也就是 TimerProc的形參idEvent,于是例子如下:
VOID CALLBACK TimerFunc(HWND hwnd,UINT uMsg,UINT_PTR idEvent,DWORD dwTime)


{

char * buf = (char*)idEvent;

printf( "%s\n", buf );//這里打印的就是"abcde"

}

DWORD CALLBACK AutoBakup( PVOID lpParam )



{

char * buf = "abcde";

MSG msg;

UINT id=SetTimer(NULL,1,1000,TimerFunc);

BOOL bRet;

while( ((bRet = GetMessage(&msg,NULL,0,0))!= 0) )


{

if(bRet==-1)


{

break;
}

else


{

TranslateMessage(&msg);

msg.wParam = (WPARAM)buf;//這里設(shè)置參數(shù)

DispatchMessage(&msg);

}

}

KillTimer(NULL,id);

return 0;

}
如果你說(shuō),這么把idEvent占了的話,真正的timer的id不就得不到了嗎?唉,暈!如果能傳遞一個(gè)指針那么就沒(méi)有設(shè)呢沒(méi)不能傳遞了,X位機(jī)器上的X 位指針是可以指遍整個(gè)虛擬內(nèi)存的,你可以把自定義參數(shù)包裝成一個(gè)結(jié)構(gòu),該結(jié)構(gòu)的一個(gè)字段指向真正的timer的id,一切..