多媒体定时器
一、簡介
在工業(yè)生產(chǎn)控制系統(tǒng)中,有許多需要定時完成的操作,如數(shù)據(jù)采集程序。Win32提供了一個基于消息機制的定時器,使用SetTimer函數(shù)創(chuàng)建一個內(nèi)存對象,設(shè)定間隔時間,當?shù)竭_要求的間隔時,計時器對象發(fā)送一個WM_TIMER消息,由相應(yīng)函數(shù)處理。但是由于WM_TIMER優(yōu)先級低,只有等待消息隊列中的其他消息都處理完畢后系統(tǒng)才會響應(yīng)該消息。而且消息隊列中的多個WM_TIMER會被合并,因此Win32定時器的精度低,不能滿足工業(yè)實時控制系統(tǒng)的要求。
本文將介紹一種精度較高的多媒體定時器,該定時器并不依賴于消息機制,可以實現(xiàn)1ms的定時精度。由于多媒體定時器另外開辟一個獨立線程執(zhí)行定時器回調(diào)函數(shù),因此當回調(diào)函數(shù)耗時較多時并不會導(dǎo)致UI的“假死”,相對而言,Win32定時器隸屬于主線程,一旦定時器回調(diào)函數(shù)耗時較多,就會導(dǎo)致UI的“假死”。
多媒體定時器相關(guān)API如下:
MMRESULTtimeGetDevCaps(
??? LPTIMECAPSptc,
??? UINTcbtc??????
??? );
函數(shù)功能:獲取定時器設(shè)備能力
參數(shù):ptc指向一個TIMECAPS型的結(jié)構(gòu),TIMECAPS有兩個成員,wPeriodMin和WperiodMax,表示定時器設(shè)備支持的最小時間周期和最大時間周期;cbtc表示TIMECAPS結(jié)構(gòu)的大小
MMRESULTtimeBeginPeriod(
??? UINTuPeriod
??? );
函數(shù)功能:設(shè)置定時器設(shè)備的最小時間分辨率
參數(shù):最小時間分辨率,以毫秒為單位
MMRESULTtimeEndPeriod(
??? UINTuPeriod
??? );
函數(shù)功能:清除之前對定時器設(shè)備的設(shè)置
參數(shù):timeBeginPeriod中指定的最小分辨率
注:timeBeginPeriod和timeEndPeriod必須配對存在,并且指定的參數(shù)值也相同。
MMRESULTtimeSetEvent(
??? UINT??????????uDelay,????
??? UINT??????????uResolution,
??? LPTIMECALLBACKlpTimeProc,
??? DWORD_PTR?????dwUser,????
??? UINT??????????fuEvent????
??? );
函數(shù)功能:創(chuàng)建并初始化定時器事件,給定定時器回調(diào)函數(shù)的入口地址
參數(shù):
uDelay:定時器觸發(fā)的時間間隔,以毫秒為單位
uResolution:定時器設(shè)備的時間精度,以毫秒為單位,應(yīng)大于或等于timeBeginPeriod中設(shè)置的值,默認為1ms,精度越高,系統(tǒng)在定時器上的負載就越大,通常選擇適合于應(yīng)用程序的最大值
LpTimeProc:定時器觸發(fā)的事件的回調(diào)函數(shù)的地址
dwUser:傳遞給回調(diào)函數(shù)的數(shù)據(jù)
fuEvent:定時類型,TIME_ONESHOT表示uDelay毫秒后只產(chǎn)生一次事件,TIME_PERIODIC表示每隔uDelay毫秒周期性的產(chǎn)生事件
MMRESULTtimeKillEvent(
??? UINTuTimerID
??? );
函數(shù)功能:刪除一個指定的定時器事件
參數(shù):指向要刪除的定時器事件的ID
void CALLBACKTimeProc(UINTuID,UINTuMsg,DWORDdwUsers,DWORDdw1,DWORDdw2);
函數(shù)功能:回調(diào)函數(shù)
參數(shù):uID,多媒體定時器的ID,ID值由timeSetEvent創(chuàng)建定時器事件時返回
uMsg,保留,當前未使用
dwUser,由timeSetEvent傳遞的用戶數(shù)據(jù)
dw1,dw2保留未使用
二、實現(xiàn)
本程序?qū)⒍嗝襟w定時器封裝成一個類MMTimer,下面是核心代碼:
#pragma once #include <mmsystem.h> #pragma comment(lib,"winmm.lib") typedef void (*TIMERCALLBACK)(DWORD); //函數(shù)的指針 class MMTimer { public:MMTimer();~MMTimer();bool Start(UINT Delay,UINT Resolution,TIMERCALLBACK lpTimeProc,DWORD fParam);void Stop(); private:bool m_RunningFlag;UINT m_ID;UINT m_Delay;TIMERCALLBACK m_pfCallback;DWORD m_Fparam;static void CALLBACK TimeProc(UINT uID, UINT uMsg, DWORD dwUser, DWORD dw1, DWORD dw2); }; #include "stdafx.h" #include "MMTimer.h" MMTimer::MMTimer() {m_pfCallback=NULL;m_Fparam=0;m_RunningFlag=false;m_Delay=0;m_ID=0; }MMTimer::~MMTimer() {if (m_RunningFlag){Stop();} } void MMTimer::TimeProc(UINT uID, UINT uMsg, DWORD dwUser, DWORD dw1, DWORD dw2) {MMTimer *ptimer=(MMTimer*)dwUser;(ptimer->m_pfCallback)(ptimer->m_Fparam); }bool MMTimer::Start(unsigned int Delay,unsigned int Resolution,TIMERCALLBACK lpTimeProc,DWORD fParam) {bool Result = true;if (!m_RunningFlag){m_pfCallback=lpTimeProc;m_Fparam=fParam;if ((m_ID=timeSetEvent(Delay,Resolution,TimeProc,(DWORD)(this), TIME_PERIODIC))!=NULL){m_RunningFlag=true;}else{Result=false;}}return Result; }void MMTimer::Stop() {if (m_RunningFlag){timeKillEvent(m_ID);m_RunningFlag=false;} }
為了對比多媒體定時器與Win32定時器的精度,我們實現(xiàn)了一個測試軟件,即定時器指定一個時間間隔(單位:ms),再測試出真實的時間間隔(采用高精度計時函數(shù)QueryPerformanceFrequency和QueryPerformanceCounter),下面是測試結(jié)果,可以發(fā)現(xiàn)win32定時器設(shè)定1ms間隔,但實際時間間隔為15.6ms,而多媒體定時器設(shè)定1ms,真實的時間間隔就是1ms 。
三、相關(guān)問題
(1)如何在回調(diào)函數(shù)中調(diào)用類內(nèi)成員
void CALLBACKTimeProc(UINTuID,UINTuMsg,DWORDdwUsers,DWORDdw1,DWORDdw2);
可以將TimeProc聲明為全局函數(shù),但通常會將其放入類中,如本程序就將其放入MMTimer類中。此時需要將其聲明為static類型(因為非靜態(tài)成員函數(shù)的指針與靜態(tài)成員函數(shù)的指針是不同的,主要是由于非靜態(tài)成員函數(shù)的參數(shù)中隱含一個this指針,導(dǎo)致函數(shù)指針的類型不匹配)。
然而將TimeProc聲明為static,它只能訪問類中的靜態(tài)成員(變量與函數(shù)),這里的解決方法是利用參數(shù)dwUsers,改參數(shù)對應(yīng)timeSetEvent中的dwUser,我們將自定義類MMTime的this指針作為參數(shù)傳進TimeProc函數(shù)中:
timeSetEvent(Delay,Resolution,TimeProc,(DWORD)(this),TIME_PERIODIC)
然后在TimeProc中使用該this指針就可以調(diào)用類中的任意成員。
(2)多媒體定時器屬于多線程的驗證如下:
在VS中加斷點調(diào)試,并選擇調(diào)試->窗口->線程,可以發(fā)現(xiàn)當開啟一個定時器后,會出現(xiàn)一個工作線程
更正-----2017.1.15
多媒體定時器回調(diào)函數(shù)應(yīng)更正為:
typedef void (CALLBACKTIMECALLBACK)(UINT uTimerID, UINT uMsg, DWORD_PTR dwUser, DWORD_PTR dw1,DWORD_PTR dw2);
timeSetEvent調(diào)用時也相應(yīng)做更改:
DWORD_PTR是DWORD型指針,其字節(jié)數(shù)與sizeof(void*)相同,我們都知道,sizeof(void*)大小是平臺相關(guān)的。在VS中將解決方案平臺設(shè)為Win32,sizeof(void*)=4,設(shè)為X64,則sizeof(void*)=8.
故之前用DOWRD類型傳遞指針是存在極大風(fēng)險的,在X64下指針為8字節(jié)但DWORD只有4字節(jié),這樣就可能出現(xiàn)如下運行錯誤:
同樣的Win32定時器回調(diào)函數(shù)第三個參數(shù)也應(yīng)該為UINT_PTR才能在x64下編譯通過。
源碼已更新!
源碼下載
參考:
[1]http://www.cnblogs.com/liuhao2638/archive/2013/06/13/3134109.html
[2] http://www.codeproject.com/Articles/1236/Timers-Tutorial
總結(jié)
- 上一篇: 昆仑通态人机界面与单片机通信实战教程二:
- 下一篇: 一文透析腾讯游戏安全反外挂能力