(轉)Windows下多媒體計時器使用舉例
在VC程式設計中,用SetTimer可以定義一個定時器,到時間了,就響應OnTimer消息,但這種定時器精度太低了。如果需要精度更高一些的定時器(精 确到1ms),可以使用下面的高精度多媒體定時器進行代碼優化,可以達到毫秒級的精度,而且使用友善。先要包含頭檔案"mmsystem.h"和庫文 件"winmm.lib"。
雖然Win95下可視化開發工具如VC、Delphi、C++ Builder等都有專用的定時器控件Timer,而且使用很友善,可以實作一定的定時功能,但最小計時精度僅為55ms,且定時器消息在多任務作業系統 中的優先級很低,不能得到及時響應,往往不能滿足實時控制環境下的應用。不過Microsoft公司在Win32 API函數庫中已經為使用者提供了一組用于高精度計時的底層函數,如果使用者使用得當,計時精度可到1ms。這個計時精度、對于一般的實時系統控制完全可以滿足要求。現将由C++ Builder 4.0提供的重新封裝後的一組與時間相關的主要接口函數(函數名、參數、功能與Win32 API基本相同)說明如下:
1.DWORD timeGetTime(void)
傳回從Windows啟動開始經過的毫秒數。最大值為232,約49.71天。
2.MMRESULT timeSetEvent(
UINT uDelay,
UINT uResolution,
LPTIMECALLBACK lpTimeProc,
DWORD dwUser,
UINT fuEvent)
該函數設定一個定時回調事件,此事件可以是一個一次性事件或周期性事件。事件一旦被激活,便調用指定的回調函數,成功後傳回事件的辨別符代碼,否則傳回NULL。參數說明如下:
uDelay:以毫秒指定事件的周期。
UResolution:以毫秒指定延時的精度,數值越小定時器事件分辨率越高。預設值為1ms。
LpTimeProc:指向一個回調函數。
DwUser:存放使用者提供的回調資料。
FuEvent:指定定時器事件類型:
TIME_ONESHOT:uDelay毫秒後隻産生一次事件
TIME_PERIODIC :每隔uDelay毫秒周期性地産生事件。
3.MMRESULT timeKillEvent(UINT uTimerID)
該函數取消一個指定的定時器回調事件。uTimerID辨別要取消的事件(由timeSetEvent函數傳回的辨別符)。如果成功則傳回TIMERR_NOERROR,如果定時器時間不存在則傳回MMSYSERR_INVALPARAM。
4.回調函數
void CALLBACK TimeProc(
UINT uID,
UINT uMsg,
DWORD dwUser,
DWORD dw1,
DWORD dw2);
該函數是一個應用程式定義的回調函數,出現定時器事件時該函數被調用。TimeProc是應用程式定義的函數名的占位符。使用該函數
時要注意的是,它隻能調用以下有限的幾組API函數:PostMessage,timeGetSystemTime, timeGetTime, timeSetEvent,timeKillEvent
,midiOutShortMsg, midiOutLongMsg,OutputDebugString。同時也不要使用完成時間很長的API函數,程式盡可能簡短。
使用以上一組函數就可以完成毫秒級精度的計時和控制(在C++Builder中使用時要将頭檔案mmsystem.h加到程式中)。由于将定時控
制精确到幾毫秒,定時器事件将占用大量的CPU時間和系統資源,是以在滿足控制要求的前提下,應盡量将參數uResolution的數值增大。而
且定時器實時控制功能完成後要盡快釋放。
注意以下幾點問題:
一、回調函數的參數不能有誤,否則可能引起程式崩掉;
二、事件調用周期uDelay不能小于事件處理時間,否則會引起程式崩潰;
三、通過dwUser給回調函數傳遞參數
例程如下:
1

MMRESULT g_wTimerID = 0;
//回調函數,參數不能有錯
2

void CALLBACK CDsisiiDlg::SendFun(UINT wTimerID, UINT msg, DWORD dwUser, DWORD dwl, DWORD dw2)
3
{
CDsisiiDlg* pdcpackerdlg = (CDsisiiDlg*)dwUser;
...
4
}
5

6

bool CDsisiiDlg::CreateTimer()
7
{
8
TIMECAPS tc;
9
UINT wTimerRes;
10
11
//設定多媒體定時器
12
if(timeGetDevCaps(&tc,sizeof(TIMECAPS))!=TIMERR_NOERROR)//向機器申請一個多媒體定時器
13
return false;
14
15
//獲得機器允許的時間間隔(一般可達到1毫秒)
16
wTimerRes=min(max(tc.wPeriodMin,1),tc.wPeriodMax);
17
18
//定時器開始工作
19
timeBeginPeriod(wTimerRes);
20
21
//每過6毫秒調用回調函數timerback(),wTimerID為定時器ID.TIME_PERIODIC表周期性調用,TIME_ONESHOT表隻産生一次事件
22
g_wTimerID = timeSetEvent(6, wTimerRes, (LPTIMECALLBACK)SendFun, (DWORD)this, TIME_PERIODIC);
23
if(g_wTimerID == 0)
24
return false;
25
26
return true;
27
}
28

29

//删除定時器
30

void CDsisiiDlg::DestroyTimer()
31
{
32
if (g_wTimerID)
33
{
34
timeKillEvent(g_wTimerID);
35
g_wTimerID = 0;
36
}
37
}
一下為在QT下使用windows多媒體計時器
在QTimer源碼分析(以Windows下實作為例) 一文中,我們看到了Qt在windows下對計時器的使用:
- 對于間隔為零的情況,Qt并沒有動用系統的計時器
- 對于間隔非零的情況
- 間隔小于20ms 且系統支援多媒體計時器,則使用多媒體計時器
- 否則,使用普通計時器
Qt 的這種政策應該能很好地滿足我們的需求了,但qtcn上一個網友還是比較期待自己直接調用系統的多媒體計時器。既然這樣,自己還是嘗試寫寫吧,寫一個自己的Timer類
代碼
- 代碼還是比較簡單的,頭檔案 mmtimer.h 如下:
#ifndef MMTIMER_H
#define MMTIMER_H
#include <qt_windows.h>
#include <QtCore/QObject>
class MMTimer : public QObject
{
Q_OBJECT
public:
explicit MMTimer(int interval, QObject *parent = 0);
~MMTimer();
signals:
void timeout();
public slots:
void start();
void stop();
friend void WINAPI CALLBACK mmtimer_proc(uint, uint, DWORD_PTR, DWORD_PTR, DWORD_PTR);
private:
int m_interval;
int m_id;
};
#endif // MMTIMER_H
- 源碼檔案 mmtimer.cpp 如下:
#include "mmtimer.h"
#include <MMSystem.h>
#ifdef __MINGW32__ //w32api bug
#define TIME_KILL_SYNCHRONOUS 0x0100
#endif
void WINAPI CALLBACK mmtimer_proc(uint timerId, uint, DWORD_PTR user, DWORD_PTR, DWORD_PTR)
{
MMTimer *t = reinterpret_cast<MMTimer*>(user);
emit t->timeout();
}
MMTimer::MMTimer(int interval, QObject *parent) :
QObject(parent),m_interval(interval),m_id(0)
{
}
MMTimer::~MMTimer()
{
stop();
}
void MMTimer::start()
{
m_id = timeSetEvent(m_interval, 1, mmtimer_proc, (DWORD_PTR)this,
TIME_CALLBACK_FUNCTION | TIME_PERIODIC | TIME_KILL_SYNCHRONOUS);
}
void MMTimer::stop()
{
if (m_id){
timeKillEvent(m_id);
m_id = 0;
}
}
說明
上面的代碼應該不需要什麼解釋了:
- timeSetEvent 和 timeKillEvent 可直接查閱 MSDN
- 另外,MinGW的win32api包,對TIME_KILL_SYNCHRONOUS沒有定義,代碼中做了一點修正
請確定正确連結所需要的庫
LIBS += -lwinmm
注意:MSDN 對timeSetEvent的介紹中這麼說的(對此不做評論)
Note This function is obsolete. New applications should use CreateTimerQueueTimer to create a timer-queue timer.