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