天天看點

(轉)Windows下多媒體計時器使用舉例 - i活着

(轉)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

(轉)Windows下多媒體計時器使用舉例 - i活着

MMRESULT g_wTimerID = 0;

      //回調函數,參數不能有錯

 2

(轉)Windows下多媒體計時器使用舉例 - i活着

void CALLBACK CDsisiiDlg::SendFun(UINT wTimerID, UINT msg, DWORD dwUser, DWORD dwl, DWORD dw2)

 3

(轉)Windows下多媒體計時器使用舉例 - i活着

{

           CDsisiiDlg* pdcpackerdlg = (CDsisiiDlg*)dwUser;

           ...

 4

(轉)Windows下多媒體計時器使用舉例 - i活着

}

 5

(轉)Windows下多媒體計時器使用舉例 - i活着

 6

(轉)Windows下多媒體計時器使用舉例 - i活着

bool  CDsisiiDlg::CreateTimer()

 7

(轉)Windows下多媒體計時器使用舉例 - i活着

 8

(轉)Windows下多媒體計時器使用舉例 - i活着

    TIMECAPS   tc;   

 9

(轉)Windows下多媒體計時器使用舉例 - i活着

    UINT wTimerRes; 

10

(轉)Windows下多媒體計時器使用舉例 - i活着

11

(轉)Windows下多媒體計時器使用舉例 - i活着

    //設定多媒體定時器  

12

(轉)Windows下多媒體計時器使用舉例 - i活着

    if(timeGetDevCaps(&tc,sizeof(TIMECAPS))!=TIMERR_NOERROR)//向機器申請一個多媒體定時器       

13

(轉)Windows下多媒體計時器使用舉例 - i活着

        return false;

14

(轉)Windows下多媒體計時器使用舉例 - i活着

15

(轉)Windows下多媒體計時器使用舉例 - i活着

    //獲得機器允許的時間間隔(一般可達到1毫秒)   

16

(轉)Windows下多媒體計時器使用舉例 - i活着

    wTimerRes=min(max(tc.wPeriodMin,1),tc.wPeriodMax);   

17

(轉)Windows下多媒體計時器使用舉例 - i活着

18

(轉)Windows下多媒體計時器使用舉例 - i活着

    //定時器開始工作   

19

(轉)Windows下多媒體計時器使用舉例 - i活着

    timeBeginPeriod(wTimerRes);   

20

(轉)Windows下多媒體計時器使用舉例 - i活着

21

(轉)Windows下多媒體計時器使用舉例 - i活着

    //每過6毫秒調用回調函數timerback(),wTimerID為定時器ID.TIME_PERIODIC表周期性調用,TIME_ONESHOT表隻産生一次事件   

22

(轉)Windows下多媒體計時器使用舉例 - i活着

    g_wTimerID = timeSetEvent(6,  wTimerRes, (LPTIMECALLBACK)SendFun,  (DWORD)this, TIME_PERIODIC);   

23

(轉)Windows下多媒體計時器使用舉例 - i活着

    if(g_wTimerID == 0)

24

(轉)Windows下多媒體計時器使用舉例 - i活着

        return false;

25

(轉)Windows下多媒體計時器使用舉例 - i活着

26

(轉)Windows下多媒體計時器使用舉例 - i活着

    return true;

27

(轉)Windows下多媒體計時器使用舉例 - i活着

}

28

(轉)Windows下多媒體計時器使用舉例 - i活着

29

(轉)Windows下多媒體計時器使用舉例 - i活着

//删除定時器

30

(轉)Windows下多媒體計時器使用舉例 - i活着

void CDsisiiDlg::DestroyTimer()

31

(轉)Windows下多媒體計時器使用舉例 - i活着

{

32

(轉)Windows下多媒體計時器使用舉例 - i活着

    if (g_wTimerID)

33

(轉)Windows下多媒體計時器使用舉例 - i活着

    {

34

(轉)Windows下多媒體計時器使用舉例 - i活着

        timeKillEvent(g_wTimerID);

35

(轉)Windows下多媒體計時器使用舉例 - i活着

        g_wTimerID = 0;

36

(轉)Windows下多媒體計時器使用舉例 - i活着

    }

37

(轉)Windows下多媒體計時器使用舉例 - i活着

}

一下為在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.      
(轉)Windows下多媒體計時器使用舉例 - i活着