天天看點

VC定時器

轉自: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

);

繼續閱讀