Windows編程第十一回 三問計時器

來源:互聯網
上載者:User

啥是計數器?

計時器是一種輸入裝置,它周期性地在每經過一個指定的時間間隔後就通知應用程式一次。當你的程式將時間間隔告訴Windows,例如“每10秒鐘通知我一聲”,然後Windows給你的程式發送周期性發生的WM_TIMER訊息以表示時間到了。

我們可以通過調用SetTimer函數為的Windows程式分配一個定時器。SetTimer有一個時間間隔範圍為1毫秒到4,294,967,295毫秒(將近50天)的整型參數,這個值指示Windows每隔多久時間給程式發送WM_TIMER訊息。例如,如果間隔為1000毫秒,那麼Windows將每秒給程式發送一個WM_TIMER訊息。

當你的程式用完定時器時,它調用KillTimer函數來停止計時器訊息。在處理WM_TIMER訊息時,你可以通過調用KillTimer函數來編寫一個“瞬間”的定時器。KillTimer調用除了會銷毀以前調用SetTimer建立的定時器事件,還會清除訊息佇列中尚未被處理的WM_TIMER訊息,從而使程式在調用KillTimer之後就不會再接收到WM_TIMER訊息。

下面就介紹一下計時器的使用方法吧。

計時器怎麼用?

如果你需要在整個程式執行期間都使用計時器,那麼你將得從WinMain函數中或者在處理WM_CREATE訊息時調用SetTimer,並在退出WinMain或響應WM_DESTROY訊息時調用KillTimer。根據調用SetTimer時使用的參數,可以選擇以下兩種方法之一來使用計時器。

 

方法一

這是最方便的一種方法,它讓Windows把WM_TIMER訊息發送到應用程式的正常視窗過程中,SetTimer調用如下所示:

SetTimer (hwnd, 1, uiMsecInterval, NULL) ;

第一個參數是其視窗過程將接收WM_TIMER訊息的視窗控制代碼。第二個參數是定時器ID,它是一個非0數值,在整個例子中假定為1。第三個參數是一個32位不帶正負號的整數,以毫秒為單位指定一個時間間隔,一個60,000的值將使Windows每分鐘發送一次WM_TIMER訊息。

你可以通過調用

KillTimer (hwnd, 1) ;

在任何時刻停止WM_TIMER訊息(即使正在處理WM_TIMER訊息)。此函數的第二個參數是SetTimer調用中所用的同一個定時器ID。在終止程式之前,你應該在響應WM_DESTROY訊息中停止任何活動的定時器。

當你的視窗過程收到一個WM_TIMER訊息①時,wParam參數等於定時器的ID值(上述情形為1),lParam參數為0。為了使程式更具有可讀性,您可以使用#define敘述定義不同的定時器ID:

#define TIMER_SEC 1

#define TIMER_MIN 2

然後你可以使用兩個SetTimer調用來設定兩個定時器:

SetTimer (hwnd, TIMER_SEC, 1000, NULL) ;

SetTimer (hwnd, TIMER_MIN, 60000, NULL) ;

WM_TIMER的處理如下所示:

caseWM_TIMER:

       

    switch (wParam)

       

    {

       

    case TIMER_SEC:

       

            //每秒一次的處理

       

                  break ;

       

    case TIMER_MIN:

       

            //每分鐘一次的處理

       

            break ;

       

    }

       

return 0 ;

       

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

 

方法二

設定計時器的第一種方法是把WM_TIMER訊息發送到通常的視窗過程,而第二種方法是讓Windows直接將計時器訊息發送給你程式的另一個函數。

接收這些計時器訊息的函數被稱為回呼函數,這是一個在你的程式之中但是由Windows調用的函數(在第四回曾提到)。你先告訴Windows此函數的地址,然後Windows調用此函數。這看起來也很熟悉,因為程式的視窗過程實際上也是一種回呼函數。當註冊視窗類別時,要將函數的地址告訴Windows,當發送訊息給程式時,Windows會調用此函數。

像視窗過程一樣,回呼函數也必須定義為CALLBACK,因為它是由Windows從程式的程式碼段調用的。callback函數的參數和callback函數的傳回值取決於callback函數的目的。跟計時器有關的callback函數中,輸入參數與視窗過程的輸入參數一樣。計時器callback函數不向Windows傳回值。

我們把以下的callback函數稱為TimerProc(你能夠選擇與其它一些用語不會發生衝突的任何名稱),它只處理WM_TIMER訊息:

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

       

{

       

           //處理WM_TIMER訊息

       

}

       

TimerProc的參數hwnd是在調用SetTimer時指定的視窗控制代碼。Windows只把WM_TIMER訊息送給TimerProc,因此訊息參數message總是等於WM_TIMER。iTimerID值是計時器ID,dwTimer值是與從GetTickCount函數的傳回值相容的值。這是自Windows啟動後所經過的毫秒數。

用第一種方法設定計時器時要求下面格式的SetTimer調用:

SetTimer (hwnd, iTimerID, iMsecInterval, NULL) ;

你使用回呼函數處理WM_TIMER訊息時,SetTimer的第四個參數由回呼函數的地址取代,如下所示:

SetTimer (hwnd, iTimerID, iMsecInterval, TimerProc) ;

 

看個例子吧。

  1 #include <windows.h>  2           3 #define ID_TIMER    1  4           5   6 LRESULT     CALLBACK       WndProc   (HWND, UINT, WPARAM, LPARAM) ;  7           8 VOID    CALLBACK   TimerProc (HWND, UINT, UINT,   DWORD ) ;  9          10  11 int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, 12          13                                                          PSTR szCmdLine, int iCmdShow) 14          15 { 16          17    static char   szAppName[]           = " Timer Demo " ; 18          19            HWND                                 hwnd ; 20          21            MSG                                  msg ; 22          23            WNDCLASS                      wndclass ; 24          25     26          27            wndclass.style                                       = CS_HREDRAW | CS_VREDRAW ; 28          29            wndclass.lpfnWndProc                                 = WndProc ; 30          31           wndclass.cbClsExtra                                  = 0 ; 32          33            wndclass.cbWndExtra                                  = 0 ; 34          35            wndclass.hInstance                                   = hInstance ; 36          37            wndclass.hIcon                                       = LoadIcon (NULL, IDI_APPLICATION) ; 38          39            wndclass.hCursor                                     = LoadCursor (NULL, IDC_ARROW) ; 40          41           wndclass.hbrBackground                       = (HBRUSH) GetStockObject (WHITE_BRUSH) ; 42          43            wndclass.lpszMenuName                        = NULL ; 44          45            wndclass.lpszClassName                       = szAppName ; 46          47     48          49            if (!RegisterClass (&wndclass)) 50          51            { 52          53                   MessageBox (  NULL, TEXT ("Program requires Windows NT!"), 54          55                                                                         szAppName, MB_ICONERROR) ; 56          57                   return 0 ; 58          59            } 60          61     62          63            hwnd = CreateWindow ( szAppName, "Timer Demo",WS_OVERLAPPEDWINDOW,CW_USEDEFAULT, CW_USEDEFAULT,CW_USEDEFAULT, CW_USEDEFAULT, 70          71                         NULL, NULL, hInstance, NULL) ; 72          73     74          75            ShowWindow (hwnd, iCmdShow) ; 76          77            UpdateWindow (hwnd) ; 78          79          80          81            while (GetMessage (&msg, NULL, 0, 0)) 82          83            { 84          85                   TranslateMessage (&msg) ; 86          87                   DispatchMessage (&msg) ; 88          89            } 90          91            return msg.wParam ; 92          93 } 94          95  96 LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) 97          98 { 99         100    switch (message)101         102     {103         104            case   WM_CREATE:105         106                   SetTimer (hwnd, ID_TIMER, 1000, TimerProc) ;107         108                  return 0 ;109         110        111         112            case   WM_DESTROY:113         114                   KillTimer (hwnd, ID_TIMER) ;115         116                   PostQuitMessage (0) ;117         118                   return 0 ;119         120            }121         122            return DefWindowProc (hwnd, message, wParam, lParam) ;123         124 }125         126 127 VOID CALLBACK TimerProc (HWND hwnd, UINT message, UINT iTimerID, DWORD dwTime)128         129 {130         131            static BOOL fFlipFlop = FALSE ;132         133            HBRUSH                        hBrush ;134         135            HDC                                  hdc ;136         137            RECT                                 rc ;138        139            fFlipFlop = !fFlipFlop ;140         141    142         143            GetClientRect (hwnd, &rc) ;144         145           hdc = GetDC (hwnd) ;146         147     hBrush = CreateSolidBrush (fFlipFlop ? RGB(255,0,0) : RGB(0,0,255)) ;148                               //括弧裡的這種判斷語句我估計大家都懂,就不解釋了149    150         151     FillRect (hdc, &rc, hBrush) ;152         153    ReleaseDC (hwnd, hdc) ;154         155    DeleteObject (hBrush) ;156         157 }

這裡計時器的時間間隔設定為1秒。當它收到WM_TIMER訊息時,它將顯示地區的顏色由藍色變為紅色或由紅色變為藍色。

程式在視窗過程處理WM_CREATE訊息時設定計時器。在處理WM_TIMER訊息處理期間,翻轉bFlipFlop的值並使視窗無效以產生WM_PAINT訊息。在處理WM_PAINT訊息處理期間,通過調用GetClientRect獲得視窗大小的RECT結構,並通過調用FillRect改變視窗的顏色。

計時器精確嗎?

很可惜它不精確,原因如下。

原因一:Windows計時器是PC硬體和ROM BIOS構造的計時器邏輯的一種相對簡單的擴充。回到Windows以前的MS-DOS編程,應用程式能夠通過捕獲稱為timer tick的BIOS中斷來實現時鐘或計時器。這些中斷每54.915毫秒產生一 次,或者大約每秒18.2次。一些為MS-DOS編寫的程式自己捕獲這個硬體中斷以實現時鐘和計時器。這是原始的IBM PC的微處理器頻率值4.772720 MHz被262144所除而得出的結果。Windows應用程式不攔截BIOS中斷,相反地,Windows本身處理硬體中斷,這樣應用程式就不必進行處理。在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秒。 

①WM_TIMER訊息。

    wParam為計數器的ID:如果需要設定多個計時器,那麼對每個計時器都使用不同的計

時器ID。wParam的值將隨傳遞到視窗過程的WM_TIMER訊息的不同而不同。

    lParam為指向TimerProc的指標,如果調用SetTimer時沒有指定TimerProc(其參數值

為NULL,即第一種用法),則lParam為0,顯然在第二種用法中此值就不為0了。

 註:部分內容參考《Windows計時器》一文

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.