Windows定時器


 

1定時器    1

1.1 創建定時器    1

1.2 銷毀定時器    1

1.3 定時器的運作    1

1.3.1 產生WM_TIMER消息    1

1.3.2 分發WM_TIMER消息    2

1.4 WM_TIMER 消息的重入    3

 

 

1定時器

1.1 創建定時器

請使用API函數 SetTimer 來創建定時器,其原型如下:

UINT SetTimer(HWND hWnd,UINT nIDEvent,UINT uElapse,TIMERPROC lpTimerFunc);

有這么兩種用法

1SetTimer(hWnd,nID,uElapse,NULL);定時給窗口 hWnd 寄送(PostMessage) WM_TIMER 消息;

2SetTimer(hWnd,nID,uElapse,TimerProc); 不論 hWnd 是否為 NULL,定時調用 TimerProc 函數。

1.2 銷毀定時器

銷毀定時器請使用KillTimer函數,其原型如下:

BOOL KillTimer(HWND hWnd,UINT uIDEvent);

1個參數應與SetTimer的第1個參數保持一致;

2個參數:如果SetTimer的第1個參數是一個有效的窗口句柄,則此參數應與SetTimer的第2個參數保持一致。否則此參數應為SetTimer的返回值。

1.3 定時器的運作

不論 SetTimer(hWnd,nID,uElapse,NULL) 還是 SetTimer(NULL,nID,uElapse,TimerProc),其實質都是處理WM_TIMER消息。

1.3.1 產生WM_TIMER消息

WM_TIMER消息並不是 Windows 系統定時、自動增加到消息隊列的,而是調用GetMessagePeekMessage的時候,才會產生WM_TIMER消息。請參考如下代碼:

void CALLBACK TimerProc(HWND hwnd,UINT uMsg,UINT idEvent,DWORD dwTime)

{

}

 

UINT TestTimer()

{

MSG msg;

UINT nTimer = SetTimer(NULL,100,1000,TimerProc);

 

Sleep(3050);

TRACE(_T("Tick=%d\n"),GetTickCount());

PeekMessage(&msg,NULL,0,0,PM_NOREMOVE);

Sleep(1050);

TRACE(_T("Tick=%d\n"),GetTickCount());

PeekMessage(&msg,NULL,0,0,PM_NOREMOVE);

 

while(PeekMessage(&msg,NULL,0,0,PM_REMOVE))

{

if(msg.message == WM_TIMER)

{

TRACE(_T("Timer=%d\n"),msg.time);

}

}

KillTimer(NULL,nTimer);

return 0;

}

Windows XP下,運行結果為:

Tick=7356593

Tick=7357656

Timer=7356593

Timer=7357656

雖然兩次Sleep的時間合計有4秒多,但消息隊列中WM_TIMER的個數並不是4個而是2個。而且這兩個WM_TIMER的時刻與兩次GetTickCount的時刻完全相等。合理的解釋是:在調用PeekMessage(&msg,NULL,0,0,PM_NOREMOVE);時,WM_TIMER消息才被創建並增加到消息隊列。如果調用GetMessagePeekMessage(&msg,NULL,0,0,PM_REMOVE);則創建的WM_TIMER消息不會被增加到消息隊列。

1.3.2 分發WM_TIMER消息

通過GetMessagePeekMessage獲得消息之后,一般會調用TranslateMessageDispatchMessage 進行消息處理。

TranslateMessage WM_TIMER 消息不做任何處理。

DispatchMessage(&msg) 負責分發 WM_TIMER 消息,其處理邏輯如下:

if(msg.lParam)

{//如果SetTimer的第4個參數不為NULL,則調用這個回調函數

TIMERPROC pfn = (TIMERPROC)msg.lParam;

pfn(msg.hwnd,WM_TIMER,msg.wParam,msg.time);

}

else

{//交給窗口過程去處理

WNDPROC pfn = (WNDPROC)GetWindowLong(msg.hwnd,GWL_WNDPROC);

CallWindowProc(pfn,msg.hwnd,WM_TIMER,msg.wParam,msg.lParam);

}

也就是說:如果SetTimer的第4個參數不為 NULL,則第1個參數所指定的 hwnd 將無法接收、處理 WM_TIMER 消息。

1.4 WM_TIMER 消息的重入

所謂重入就是當前的消息還沒有處理完畢就進入下一個消息的處理。因為WM_TIMER消息是入隊消息,所以一般情況下,對WM_TIMER的處理是不會重入的。但也有特殊情況,請參考如下代碼:

//定義定時器處理函數

void CALLBACK TimerProc(HWND hwnd,UINT uMsg,UINT idEvent,DWORD dwTime)

{

TRACE(_T("進入 OnTimer=%lu\n"),dwTime);

Sleep(3500);

MSG msg;

while(PeekMessage(&msg,NULL,0,0,PM_REMOVE))

{

TranslateMessage(&msg);

DispatchMessage(&msg);

}

Sleep(1000);

TRACE(_T("離開 OnTimer=%lu\n"),dwTime);

}

//設置定時器

SetTimer(100,1000,TimerProc);

本來TimerProc一秒被調用一次,現在情況發生了變化:在TimerProc內部,Sleep(3500)后再調用PeekMessage會立即產生WM_TIMER消息。DispatchMessage會再次調用TimerProc函數處理這個消息。結果就是TimerProc函數無限制的遞歸調用自己,永遠不會返回,最終會因為棧空間溢出而導致程序異常退出。

為了防止TimerProc函數的重入並可能引起的程序崩潰,就需要阻止重入TimerProc函數。可行的方法之一如下:

void CALLBACK TimerProc(HWND hwnd,UINT uMsg,UINT idEvent,DWORD dwTime)

{

static bool bWorking = false; //是否正在處理 WM_TIMER 消息

if(bWorking)

{//如果正在處理 WM_TIMER 消息則返回,這樣就防止了重入

return;

}

bWorking = true;        //標記正在處理 WM_TIMER 消息

... ... ...            //處理 WM_TIMER 消息

bWorking = false;        //標記 WM_TIMER 消息已經處理完畢

}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM