轉自:http://hi.baidu.com/asd4790007/item/9d14953fb111b20bcfb9fed7
在VC中,定時有三種方法,一是利用WM_TIMER消息的API函數,二是使用多媒體定時器,三是多線程定時器(不知道是不是可以這樣分啊)。
1、WM_TIMER
SetTimer函數是用來設立一個定時器,SetTimer函數的原型如下:
UINT_PTR SetTimer(
HWND hWnd, // 視窗句柄
UINT_PTR nIDEvent, // 定時器ID
UINT uElapse, // 時間間隔,機關為毫秒
TIMERPROC lpTimerFunc // 回調函數
);
第一個參數是視窗句柄,在MFC中,SetTimer函數被封裝在CWnd類中,調用時不用指出視窗句柄;
第二個參數是定時器ID,在啟用多個定時器時,用來辨別各個不同的定時器,在不使用MFC的情況下,當接收到WM_TIMER消息時,WPARAM wParam就是這個ID(API的東西,都忘得差不多了,-_-);
第三個參數為時間間隔,也就是回調函數的調用周期,機關是毫秒;
第四個參數是回調函數,當設為NULL時,調用系統預設的回調函數。這個預設的回調函數是OnTimer,可以在需要定時器的類中添加,添加時隻要在ClassWizard裡添加WM_TIMER的消息映射就可以了。
函數的傳回值為定時器ID。
這個函數的使用有點像定時器中斷,SetTimer就是開中斷,回調函數就是中斷子程,既然有開中斷就一定要有關中斷,在VC裡面用KillTimer來取消定時器。
KillTimer函數的原型如下:
BOOL KillTimer(
HWND hWnd, // 視窗句柄
UINT_PTR uIDEvent // ID
);
與SetTimer一樣,當在MFC中使用時,不用指定視窗句柄。正确取消定時器則傳回true,否則傳回false.
前面說到SetTimer第四個參數為回調函數,不設為NULL時,它就是一個回調函數的位址。回調函數格式如下:
void CALLBACK TimerProc(HWND hWnd,UINT nMsg,UINT nTimerid,DWORD dwTime);
第一個參數是視窗句柄,第二個是消息,第三個是定時器ID,必須與SetTimer中的一緻,最後一個是回調函數中要使用的參數。
例:
SetTimer(1,1000,NULL);
SetTimer(2,2000,NULL);
//這樣産生了兩個定時器,我們在OnTimer函數中對兩個不同的定時器作不同的處理
void C****::OnTimer(UINT nIDEvent)
{
switch(nIDEvent)
{
case 1:
Timer1Proc();
break;
case 2:
Timer2Proc();
break;
}
}
//當使用回調函數時(上面SetTimer函數第三個參數不用NULL),nTimerid用來判斷是哪個定時器
void CALLBACK TimerProc(HWND hWnd,UINT nMsg,UINT nTimerid,DWORD dwTime)
{
switch(nTimerid)
{
case 1: ///處理ID為1的定時器
Timer1Proc();
break;
case 2: ///處理ID為2的定時器
Timer2Proc();
break;
}
}
最後不要忘了取消掉定時器
KillTimer(1);
KillTimer(2);
差點忘了說注意事項了,回調函數的處理時間一定不能夠長于定時器的時間間隔,否則的話,hiahia,後果很嚴重。。。
、多媒體定時器
前面提到的通過SetTimer來設定定時器的方法,操作起來很簡單,但是精度不高,隻能用于精度要求不高的場合,在精度要求稍高的場合中,可以用多媒體定時器。
多媒體定時器有兩種使用方法。
1)timeGetTime函數:定時精度為ms級,傳回從Windows啟動開始所經過的時間。該函數是通過查詢的方式來進行定時的,是以在程式中,必須建立一個循環來不斷地查詢以便進行定時,是以這個函數通常都在循環(do-while或者for循環)中。
2)timeSetEvent函數,原型如下:
MMRESULT timeSetEvent(UINT uDelay,UINT uResolution,LPTIMECALLBACK lpTimeProc,
DWORD dwUser,UINT fuEvent);
第一個參數uDelay:延遲時間,也就是多久調用一定回調函數,機關為毫秒;
第二個參數uResolution:時間精度,機關為毫秒,預設時為1ms;
第三個參數lpTimeProc:回調函數;
第四個參數參數dwUser:使用者提供的回調資料;
第五個參數fuEvent:定時器的事件類型,可以有兩種取值,TIME_ONESHOT表示執行一次;TIME_PERIODIC表示周期性執行,調用周期為uDelay。
回調函數格式為:
void CALLBACK TimeProc(UINT uID,UINT uMsg,DWORD dwUser,DWORD dw1,DWORD dw2);
參數uID是該多媒體定時器的辨別,dwUser必須與timeSetEvent中的DwUser一緻,傳遞回調函數中需要使用的參數。
回調函數必須聲明為PASCAL全局函數,否則編譯時會有問題。
如:
void PASCAL TimeProc(UINT wTimerID, UINT msg,DWORD dwUser,
DWORD dwl,DWORD dw2)
{}
成功後傳回事件的辨別符代碼,否則傳回NULL。
timeSetEvent函數的使用不像SetTimer那樣簡單。一般我們在使用之前,要先确認系統的分辨率的取值範圍,無誤之後才開始使用。
例:
TIMECAPS tc;
//利用函數timeGetDevCaps取出系統分辨率的取值範圍,如果無錯則繼續;
if(timeGetDevCaps(&tc,sizeof(TIMECAPS))==TIMERR_NOERROR)
{
wAccuracy=min(max(tc.wPeriodMin,TIMER_ACCURACY),tc.wPeriodMax); //分辨率的值不能超出系統的取值範圍
//調用timeBeginPeriod函數設定定時器的分辨率
timeBeginPeriod(wAccuracy);
//設定定時器
timeSetEvent(nDelay,wAccuracy,lpTimeProc, dwUser,TIME_PERIODIC);
}
在精度要求較高的情況下,如要求定時誤差不大于1ms時,可以利用GetTickCount函數,它傳回自計算機啟動後的時間,傳回值是DWORD型,機關為毫秒。通過兩次調用GetTickCount函數,然後控制它們的內插補點來取得定時效果。GetTickCount函數不帶參數。與前面提到的timeGetTime類似的,必須有定時查詢。
前面多次提到查詢這個詞,下面就舉個例子來說明怎麼來實作吧。下面的一小段程式實作的是一個50ms的定時。
DWORD dwStart, dwStop ;
dwStop = GetTickCount();
while(TRUE)
{
// 上一次的中止值變成新的起始值,開始新一次的定時
dwStart = dwStop ;
// 這裡可以添加處理程式
do
{
dwStop = GetTickCount() ; //查詢時間
}
while(dwStop-50 < dwStart) ; //50ms到則退出循環
}
最後說一下注意事項:
1)、任務處理的時間不能大于周期間隔時間。
2)、在定時器使用完畢後,應及時調用timeKillEvent将其釋放。
3)、多媒體定時器執行後會啟動額外的線程(線程這東西一直都沒弄明白過,-_-)。
4)、由于多媒體定時器是另啟動線程處理定時操作,是以在.回調函數中隻能通路本線程的MFC對象、不能調用任何系統函數,除了PostMessage, timeGetSystemTime, timeGetTime, timeSetEvent, timeKillEvent,midiOutShortMsg, midiOutLongMsg, OutputDebugString等。
5、使用多媒體定時器時,必須在工程裡包含winmm.lib。
用前面的兩種方法取得的定時效果在許多場合已經滿足實際的要求,但它們的精度隻有毫秒級的,這樣在要求定時時間間隔小時,實際定時誤差就很大,比如說定時1ms,在使用多媒體定時器時,由于它還要啟動額外的線程,這一部分的開銷相比1ms來說還是相當可觀的。
對于精确度要求更高的定時操作,則應該使用QueryPerformanceFrequency和QueryPerformanceCounter函數。這兩個函數是Visual C++提供并且隻能在Windows 95及其後續版本中使用,其精度與CPU的時鐘頻率有關,它們要求計算機從硬體上支援精确定時器。
QueryPerformanceFrequency函數和QueryPerformanceCounter函數的原型如下:
BOOL QueryPerformanceFrequency (LARGE_INTEGER *lpFrequency);
BOOL QueryPerformanceCounter (LARGE_INTEGER *lpCount);
上述兩個函數的參數的資料類型LARGE_INTEGER既可以是一個8位元組長的整型數,也可以是兩個4位元組長的整型數的聯合結構,其具體用法根據編譯器是否支援64位而定。該類型的定義如下:
typedef union _LARGE_INTEGER
{
struct{
DWORD LowPart ; // 4位元組整型數
LONG HighPart ; // 4位元組整型數
};
LONG QuadPart ; // 8位元組整型數
} LARGE_INTEGER ;
使用QueryPerformanceFrequency和QueryPerformanceCounter函數進行精确定時的步驟如下:
1)、調用QueryPerformanceFrequency函數取得高精度運作計數器的頻率f,機關是Hz,此數一般很大,我在我的電腦測試了一下,是3579545,呵呵;
2)、在需要定時的代碼的兩端分别調用QueryPerformanceCounter函數以取得高精度運作計數器的數值n1、n2,兩次數值的內插補點通過f換算成時間間隔,t=(n2-n1)/f,當t大于或等于定時時間長度時,啟動定時器;
到現在也沒有說到多線程的問題。其實這個方法還是前面說到的查詢的方法,隻不夠使用的查詢工具不同罷了。之是以把它單列出來,因為它的精度最高的,哈哈哈。
至于多線程呢,其實和這兩個函數關系也不是那麼大,在程式中另外啟動一個線程就成了。要實作多線程,一般用AfxBeginThread函數,這個函數的原型為:
CWinThread* AfxBeginThread(
AFX_THREADPROC pfnThreadProc,//線程函數位址
LPVOID pParam,//線程參數
int nPriority = THREAD_PRIORITY_NORMAL,//線程優先級
UINT nStackSize = 0,//線程堆棧大小,預設為1M
DWORD dwCreateFlags = 0,
LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL
);
CWinThread* AfxBeginThread(
CRuntimeClass* pThreadClass,
int nPriority = THREAD_PRIORITY_NORMAL,
UINT nStackSize = 0,
DWORD dwCreateFlags = 0,
LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL
);