天天看點

C++ Timer定時器

什麼時候我們需要用到SetTimer函數呢?當你需要每個一段時間執行一件事的的時候就需要使用SetTimer函數了。

讓我們先來看看SetTimer函數的原型:

 UINT SetTimer(UINT nIDEvent,  UINT nElapse,   void(CALLBACK EXPORT *lpfnTimer)(HWND,UINT ,YINT ,DWORD))

當使用SetTimer函數的時候,就會生成一個計時器。

第一個參數:函數中nIDEvent指的是計時器的辨別,也就是名字。

第二個參數:nElapse指的是時間間隔,也就是每隔多長時間觸發一次事件。

第三個參數:是一個回調函數,在這個函數裡,放入你想要做的事情的代碼,你可以将它設定為NULL,也就是使用系統預設的回調函數onTimer。

onTimer這個函數怎麼生成的呢?——在需要計時器的類的生成onTime函數:在ClassWizard裡,選擇需要計時器的類,添加WM_TIME消息映射,就自動生成onTime函數了。然後在函數裡添加代碼,讓代碼實作功能。每隔一段時間就會自動執行一次。

例: SetTimer(1,1000,NULL);

 1:計時器的名稱; 1000:時間間隔,機關是毫秒; NULL:使用onTime函數。

當不需要計時器的時候調用KillTimer(nIDEvent); 例如:KillTimer(1);

2. 或許你會問,如果我要加入兩個或者兩個以上的 timer怎麼辦?繼續用SetTimer函數吧,上次的timer的ID是1,這次可以是2,3,4。。。。 SetTimer(2,1000,NULL); SetTimer(3,500,NULL); 嗯,WINDOWS會協調他們的。

當然onTimer函數體也要發生變化,要在函數體内添加每一個timer的處理代碼:

onTimer(nIDEvent) 

{ switch(nIDEvent) 

                      { case 1:........; break; 

                         case 2:.......; break; 

                         case 3:......; break; 

                      } 

}

***********************************************************

SDK程式設計筆記 — 計時器篇

兩個計時器API的讨論

  SetTimer函數用于建立一個計時器,KillTimer函數用于銷毀一個計時器。計時器屬于系統資源,使用完應及時銷毀。

  SetTimer的函數原型如下:

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

  其中

  hWnd是和timer關聯的視窗句柄,此視窗必須為調用SetTimer的線程所有;如果hWnd為NULL,沒有視窗和timer相關聯并且nIDEvent參數被忽略 

  nIDEvent是timer的辨別,為非零值;如果hWnd為NULL則被忽略;如果hWnd非NULL而且與timer相關聯的視窗已經存在一個為此辨別的timer,則此次SetTimer調用将用新的timer代替原來的timer。timer辨別和視窗相關,兩個不同的視窗可以擁有 nIDEvent相同的tiemr 

  uElapse是以毫秒指定的計時間隔值,範圍為1毫秒到4,294,967,295毫秒(将近50天),這個值訓示Windows每隔多久時間給程式發送WM_TIMER消息。 

  lpTimerFunc是一個回調函數的指針,俗稱TimerFunc;如果lpTimerFunc為NULL,系統将向應用程式隊列發送 WM_TIMER消息;如果lpTimerFunc指定了一個值,DefWindowProc将在處理WM_TIMER消息時調用這個 lpTimerFunc所指向的回調函數,是以即使使用TimerProc代替處理WM_TIMER也需要向視窗分發消息。

  關于SetTimer的傳回值:如果hWnd為NULL,傳回值為建立立的timer的ID,如果hWnd非NULL,傳回一個非0整數,如果SetTimer調用失敗則傳回0

  KillTimer的函數原型為:BOOL KillTimer( HWND hWnd, UINT_PTR uIDEvent ) ; 參數意義同SetTimer。 

  關于KillTimer對消息隊列中剩餘未處理的WM_TIMER消息的影響,MSDN和Programming Windows上的說法完全相反。MSDN的說法很幹脆:The KillTimer function does not remove WM_TIMER messages already posted to the message queue. 而petzold則說 The KillTimer call purges the message queue of any pending WM_TIMER messages. Your program will never receive a stray WM_TIMER message following a KillTimer call. (KillTimer消除消息隊列中任何未處理的WM_TIMER消息,調用KillTimer後你的程式永遠不會收到一條“漂泊遊蕩”的WM_TIMER消息)

關于WM_TIMER消息

  wParam為計時器的ID;如果需要設定多個計時器,那麼對每個計時器都使用不同的計時器ID。wParam的值将随傳遞到視窗過程中的WM_TIMER消息的不同而不同。

  lParam為指向TimerProc的指針,如果調用SetTimer時沒有指定TimerProc(參數值為NULL),則lParam為0(即NULL)。 

  可以通過在視窗過程中提供一個WM_TIMER case處理這個消息,或者,預設視窗過程會調用SetTimer中指定的TimerProc來處理WM_TIMER消息

使用計時器的三種方法

  如果在程式的整個執行過程中使用計時器,一般在處理WM_CREATE消息時或WinMain中消息循環前調用SetTimer,在處理 WM_DESTROY消息時或在WinMain中消息循環後return前調用KillTimer。根據SetTimer中的參數不同,有三種方法使用計時器。

  方法一:調用SetTimer時指定視窗句柄hWnd,nIDEvent中指定計時器ID,将lpTimerFunc置NULL進而不使用TimerProc;在視窗過程中處理WM_TIMER消息。調用KillTimer時,使用SetTimer中指定的hWnd和id。最好使用#define定義timer的id,例如:

#define ID_TIMER 1 

SetTimer(hWnd,ID_TIMER,1000,NULL) ;

KillTimer(hWnd,ID_TIMER) ;

  方法二:調用SetTimer時指定視窗句柄hWnd,nIDEvent中指定計時器ID,lpTimerFunc參數不為NULL而指定為TimerProc函數的指針。這種方法使用TimerProc函數(名字可自定)處理WM_TIMER消息:

VOID CALLBACK TimerProc ( HWND hwnd, UINT message, UINT iTimerID, DWORD dwTime)

{

 //處理WM_TIMER訊息 

}

   TimerProc的參數hwnd是在調用SetTimer時指定的視窗句柄。Windows隻把WM_TIMER消息送給TimerProc,是以消息參數總是等于WM_TIMER。iTimerID值是計時器ID,dwTimer值是與從GetTickCount函數的傳回值相容的值。這是自 Windows啟動後所經過的毫秒數。 使用這種方法時,相關函數調用的形式為:

SetTimer(hWnd,ID_TIMER,1000,TimerProc) ;

KillTimer(hWnd,ID_TIMER) ;

  方法三:調用SetTimer時不指定視窗句柄(為NULL),iTimerID參數自然被忽略,lpTimerFunc不為NULL而指定為TimerProc的指針。正如上面SetTimer的讨論中所說的,此時SetTimer的傳回值正是建立立的計時器的ID,需将這個ID儲存以供KillTimer銷毀計時器時所用。當然,KillTimer的hWnd參數也置為NULL。這種方法同樣用TimerProc處理WM_TIMER消息。

UINT_PTR iTimerID ; 

iTimerID = SetTimer(NULL,0,1000,TimerProc) ;

KillTimer(NULL,iTimerID) ;

  使用這種方法的好處是不必自己指定計時器ID,這樣就不必擔心用錯ID。

使用多個計時器

  使用多個計時器隻要在建立計時器時指定不同的ID。比如用上面所述方法一時的情況:

#define TIMER_SEC 1

#define TIMER_MIN 2

然後使用兩個SetTimer來設定兩個計時器:

SetTimer (hwnd, TIMER_SEC, 1000, NULL) ;

SetTimer (hwnd, TIMER_MIN, 60000, NULL) ; 

WM_TIMER的處理如下所示:

case WM_TIMER:

 switch (wParam)

 {

  case TIMER_SEC:

   //每秒一次的處理 

   break ;

  case TIMER_MIN:

   //每分鐘一次的處理

   break ;

 }

 return 0 ; 改變計時器的時間間隔

  如果想将一個已經存在的計時器設定為不同的時間間隔,可以簡單地用不同的時間值再次調用SetTimer。

計時器精确嗎?

  計時器并不精确。有兩個原因:

  原因一:Windows計時器是硬體和ROM BIOS架構下之計時器一種相對簡單的擴充。回到Windows以前的MS-DOS程式寫作環境下,應用程式能夠通過攔截者稱為timer tick的BIOS中斷來實作時鐘或計時器。一些為MS-DOS編寫的程式自己攔截這個硬體中斷以實作時鐘和計時器。這些中斷每54.915毫秒産生一次,或者大約每秒18.2次。這是原始的IBM PC的微處理器頻率值4.772720 MHz被218所除而得出的結果。在Windows 98中,計時器與其下的PC計時器一樣具有55毫秒的解析度。在Microsoft Windows NT中,計時器的解析度為10毫秒。Windows應用程式不能以高于這些解析度的頻率(在Windows 98下,每秒18.2次,在Windows NT下,每秒大約100次)接收WM_TIMER消息。在SetTimer中指定的時間間隔總是截尾後tick數的整數倍。例如,1000毫秒的間隔除以 54.925毫秒,得到18.207個tick,截尾後是18個tick,它實際上是989毫秒。對每個小于55毫秒的間隔,每個tick都會産生一個 WM_TIMER消息。

  可見,計時器并不能嚴格按照指定的時間間隔發送WM_TIMER消息,它總要相差那麼幾毫秒。

  即使忽略這幾個毫秒的差别,計時器仍然不精确。請看原因二:

  WM_TIMER消息放在正常的消息隊列之中,和其他消息排列在一起,是以,如果在SetTimer中指定間隔為1000毫秒,那麼不能保證程式每 1000毫秒或者989毫秒就會收到一個WM_TIMER消息。如果其他程式的執行事件超過一秒,在此期間内,您的程式将收不到任何WM_TIMER訊息。事實上, Windows對WM_TIMER消息的處理非常類似于對WM_PAINT消息的處理,這兩個消息都是低優先級的,程式隻有在消息隊列中沒有其他消息時才接收它們。

  WM_TIMER還在另一方面和WM_PAINT相似:Windows不能持續向消息隊列中放入多個WM_TIMER訊息,而是将多餘的WM_TIMER消息組合成一個消息。是以,應用程式不會一次收到多個這樣的消息,盡管可能在短時間内得到兩個WM_TIMER消息。應用程式不能确定這種處理方式所導緻的WM_TIMER消息「遺漏」的數目。

  可見,WM_TIMER消息并不能及時被應用程式所處理,WM_TIMER在消息隊列中的延誤可能就不能用毫秒來計算了。

  由以上兩點,你不能通過在處理WM_TIMER時一秒一秒計數的方法來計時。如果要實作一個時鐘程式,可以使用系統的時間函數如 GetLocalTime ,而在時鐘程式中,計時器的作用是定時調用GetLocalTime獲得新的時間并重新整理時鐘畫面,當然這個重新整理的間隔要等于或小于1秒。

繼續閱讀