本文說明兩個問題:
1.windows的消息處理機制;
2.怎么往SetTimer的回調函數傳遞參數。
首先看第一個問題,我們都知道windows是消 息驅動的,windows呈現給用戶的任何可以看到聽到的東西幾乎都是消息驅動的,在底層windows為每個線程準備了一個消息隊列,如果用戶線程注冊 了某個消息,那么在適當的時候windows就會將消息投遞到該線程的消息隊列中,然后由該線程取出隊列中的消息,然后處理之,這個過程有兩個參與者,一 個是windows系統,它主要負責投遞消息,收不收是用戶線程的事,另一個就是用戶線程,它主要負責取出消息并處理消息,即使用戶線程因為睡眠或者根本就沒有設定消息循環,系統還是會投遞的,系統和用戶線程的消息接口就是消息隊列,這就在用戶和系統之間關于消息解除了耦合,在用戶線程處理消息的時候,其實還有一個消息隊列,因為一個線程不一定只接收一種消息而且不一定馬上就能處理完并返回,這個消息隊列我們把它叫做消息分發隊列或者簡稱分發隊列用來與系統的消息隊列區分,注意分發隊列里面的消息都是已經格式化后的消息,分發給誰呢?當然是分發給消息的回調函數了,對于有窗口的就是先分發給窗口過程,然后 由窗口過程分發給具體的處理函數。
下面我們來通過一個例子說明一下,用vs2005或VC建立一個Win32工程,然后看自動生成的代碼:
int APIENTRY _tWinMain(HINSTANCE hInstance,

HINSTANCE hPrevInstance,

LPTSTR lpCmdLine,

int nCmdShow)


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

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

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


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

switch (message)//message就是消息號

{
case WM_COMMAND:


default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
以上實際上就是windows消息機制的全景,對于windows的timer當然也要套用上面的模式了,在SetTimer調用后,實際上就注冊了WM_TIMER消息,以下是函數定義:
UINT_PTR SetTimer(

HWND hWnd,

UINT_PTR nIDEvent,

UINT uElapse,

TIMERPROC lpTimerFunc

);
lpTimerFunc就是回調函數,其形式為:
VOID CALLBACK TimerProc(

HWND hwnd,

UINT uMsg,

UINT_PTR idEvent,

DWORD dwTime

);
SetTimer的參數uElapse就是時間間隔,比如設置為1000即1秒,現在有了一個問題,請看下列代碼:
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的處理函數中睡眠了,那么SetTimer時的1000毫秒觸發一次timer回調還會進行嗎?其實不會進行了,本質上那個1000毫秒就 不是說觸發回調函數的間隔,而是產生WM_TIMER消息的間隔,因為回調函數中睡眠了,所以也就阻塞了本線程,這個線程就不再往前走了,但是底層的 WM_TIMER消息也會因此而不再投遞嗎?不會,消息的投遞其實不是本線程進行的,而是系統進行的,本線程已經睡眠了,但是系統卻沒有睡眠,它會繼續往該線程的消息隊列投遞WM_TIMER消息,只不過這些消息不再由該線程的GetMessage取出了,因為它睡眠了(如果對回調函數是否和 GetMessage是否為統一線程有疑義,那么用GetCurrentThreadId()檢測一下就好),消息全部堆積在隊列里面,然后等待睡眠結束后再一個一個處理。好吧,這個問題解決了,下一個問題又來了,試著將回調函數改為下面的:
VOID CALLBACK TimerFunc(HWND hwnd,UINT uMsg,UINT_PTR idEvent,DWORD dwTime)


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

typedef struct tagMSG
{ // msg

HWND hwnd;

UINT message;

WPARAM wParam;

LPARAM lParam;

DWORD time;

POINT pt;

} MSG;
看看wParam和lParam,MSDN上說是額外參數,我們只需要重新設置值就可以了,于是我們要取wParam和lParam這兩個參數與 TimerProc形參的交集,這個交集就是我們可以自定義的參數,經過測試,發現wParam其實就是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;//這里設置參數

DispatchMessage(&msg);

}

}

KillTimer(NULL,id);

return 0;

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