1.前言。
我們知道,在Windows中,可以通過調用SetTimer函數為應用程式分配一個計時器。當
指定了一個時間間隔以後,Windows系統將每隔指定的時間嚮應用發送一條WM_TIMER消
息,從而使應用程式能夠實現許多與時間相關的動作。
然而需要指出的是,由系統發給應用程式的WM_TIMER訊息並不是非同步,這條訊息被
放在常規的訊息佇列中,並與其它訊息一起排序。因此,即使我們在調用SetTimer()
時設定了1000毫秒的時間間隔,應用程式卻不一定保證每隔一秒鐘接受到一條
WM_TIMER訊息,如果另一個程式的忙碌時間超過一秒鐘,那麼我們的應用程式在那段
時間內就不能接收到任何WM_TIMER訊息。
顯然,這種情況的存在對那些需要精確時間間隔的應用(如某些監控程式)來說是致命
的。所幸的是,在Windows中隱藏著某些機制,使得我們能夠獲得精確計時器服務。
2.系統計時器.
在Windows的SYSTEM.DRV驅動程式中提供了幾個鮮為人知的系統計時器函數(這幾個函
數未寫入Windows.h中,但卻被SYSTEM.DRV輸出了),這幾個函數可以協助我們獲得精
確計時器服務,即系統計時器。這其中最重要的是CreateSystemTimer()和
KillSystemTimer(),這兩個函數允許我們安裝非同步計時器的回呼函數(Callback),有
些類似於在DOS環境中截取INT 8中斷處理常式。這個回調是真正非同步,完全避開了
Windows的訊息工具,因而具有重要意義。事實上,Microsoft Excel和Windows COMM
驅動程式都用到了系統計時器,而由SetTimer()安裝的一般計時器也是由系統計時器
來實現的。
這兩個函數的原型如下:
WORD CreateSystemTimer(wMsecInterval,lpfnTimerProc);
WORD wMsecInterval; /*以毫秒為單位的時間間隔,系統將每隔此時間調用一 次回呼函數
*/
FARPROC lpfn TimerProc;/*指向回呼函數的指標*/
WORD KillSystemTimer(hTimer);
WORD hTimer;/*欲釋放的系統計時器控制代碼*/
其中,CreateSystemTimer()用於安裝一個系統計時器回呼函數,SYSTEM INT8處理常式將
按wMsecInterval指定的時時間間隔調用此回呼函數。當然,這個指定的回調頻率也是有
限的,同SetTimer()一樣,每秒鐘調用回呼函數次數不能超過18.2次,即wMsecInterval>
55。該函數返回一個系統計時器控制代碼。若安裝失敗,則返回NULL。KillSystemTimer()則
用於撤銷一個已安裝的系統計時器hTimer。若成功,則返回;出錯則返回傳給它的參數
hTimer。
3.使用系統計時器應注意的問題。
系統計時器回呼函數雖然不是中斷處理常式,但由於它直接被中斷處理常式調用,因此也
必將它看作中斷代碼。這也就決定了在使用過程中必須注意以下幾個問題:
(1).在回函數中應包括盡量少的代碼,以使得頻繁回調的該函數不至於佔用太多的CPU時
間。一般情況下,系統計時器總是用來監視或設定某些變數的值。
(2).由於該回呼函數屬於中斷代碼,因此大多數Windows API函數調用都不適用了,只有
幾個簡單的函數仍然可以使用,如PostMessage(),GetCurrentTask()和MessageBeep()
等。
(3).由於該回呼函數由中斷處理常式直接調用,因此該函數必須放在一個固定的程式碼片段
中,並且調用前必須裝載DS寄存器,這可由形實替換函數MakeProcInstance()來做到。
另外,由於這兩個函數在Windows.h中沒有給出(即Windows預設輸入庫不含此兩函數),因
此在調用之前必須進行連結。這可採用運行時動態連結,即通過GetModuleHandle()和
GetProcAddress()來連結;也可在程式模組定義檔案中用IMPORTS語句來引入,此時則必
須在程式源檔案中說明CreateSystemTimer()和KillSystemTimer()為外部函數。本文給出
的例子採用第二種方案。
4.一個例子。
本文最後給出一個簡單的例子,以說明系統計時器是如何工作的。
在本例中,我們安裝了一個每秒鐘調用一次的回呼函數,該回呼函數發出一聲蜂鳴。為了
測試該系統計時器,我們特意編寫了一段較長時間的迴圈語句。在這段迴圈中,由
SetTimer()
安裝的通常計時器是不能工作的(因為Windows是一個非搶先的系統),而我們安裝的系統
計時器仍然能每隔一秒鐘發出一聲蜂鳴。
該例子在MSVC++1.5中調試通過,運行良好。
//SystemT.c
#include <windows.h>
extern WORD WINAPI CreateSystemTimer(WORD wTimeOut,FARPROC lpfnTimerProc);
extern WORD WINAPI KillSystemTimer(WORD hTimer);
void FAR PASCAL_export MyTimerProc(void);
WORD SetUpSystemTimer(WORD wTimeOut);
BOOL ClearSystem Timer(WORD hTm);
FARPROC fpTimerProc=NULL;
WORD hTimer=NULL;
char szAppName[]= "SystemTimer ";
int PASCAL WinMain(HANDLE hInstance,HANDLE hPrevInstance,
LPSTR lpszCmdParam,int nCmdShow)
{
WNDCLASS wc;
HWND hWndMain;
int i,j;
HCURSOR hcurSave;
if(hPrevInstance=NULL){
wc.lpszMenuName =NULL; wc.lpszClassName =szAppName; wc.hInstance =hInstance; wc.hIcon =LoadIcon(NULL,IDI_APPLICATION);
wc.hCursor =LoadCursor(NULL,IDC_ARROW);
wc.hbrBackground =(HBRUSH)COLOR_WINDOW+1;
wc.style =0; wc.lpfnWndproc =DefWindowProc;
wc.cbClsExtra =0; wc.cbWndExtra =0;
if(!RegisterClass(&wc))
return(0);
}
if(hWndMain=CreateWindow(szAppName,
szAppName,WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,CW_USEDEFAULT,
CW_USEDEFAULT,CW_USEDEFAULT,
NULL,NULL,hInstance,NULL))=NULL)
return(0);
ShowWindow(hWndmain,nCmdShow);
Update Window(hWndMain);
fpTimerProc=MakeProcInstance((FARPROC)MyTimerProc,hInstance);
if(hTimer=SetUpSystemTimer(1000))=NULL{
MessageBox(hwndMain, "Set System Timer Error ",
szAppName,MB_ICONEXCLAMATION:MB_OK);
return 0;
}
hcurSave=SetCursor(LoadCursor(NULL,IDC_WAIT));
for(i=0;i <10000;i++)
for(j=0;j <10000;j++)
SetCursor(hcurSave);
ClearSystemTime(hTimer);
}
WORD SetUpSystemTime(WORD wTimeOut)
{
WORD hTm;
if((hTm=CreateSystemTimer(wTimeOut,fpTimerProc))=NULL){
fpTimerProc=NULL:
return NULL
}else return hTm;
}
BOOL ClearSystemTimer(WORD hTm)
{
if(hTm){
if(KillSystemTimer(hTm)!=0)
return FALSE;
hTm=NULL;
}
return TRUE
voidFAR PASCAL_export MyTimerProc(void)
{
MessageBeep(0);
}
////////////////////////////////////
//SystemT.def
NAME SystemTimer
DESCRIPTION 'System Timer '
EXETYPE WINDOWS
STUB 'WINSTUB '
CODE PRELOAD
DATA PRELOAD MOVABLE MULTIPLE
HEAPSIZE 1024
STACKSIZE 8192
EXPORTS MyTimerProc
IMPORTS CreateSystemTimer=SYSTEM.CREATESYSTEMTIMER
KillSystemTimer=SYSTEM.KILLSYSTEMTIMER