• 多媒体定时器


    一、简介

    在工业生产控制系统中,有许多需要定时完成的操作,如数据采集程序。Win32提供了一个基于消息机制的定时器,使用SetTimer函数创建一个内存对象,设定间隔时间,当到达要求的间隔时,计时器对象发送一个WM_TIMER消息,由相应函数处理。但是由于WM_TIMER优先级低,只有等待消息队列中的其他消息都处理完毕后系统才会响应该消息。而且消息队列中的多个WM_TIMER会被合并,因此Win32定时器的精度低,不能满足工业实时控制系统的要求。

    本文将介绍一种精度较高的多媒体定时器,该定时器并不依赖于消息机制,可以实现1ms的定时精度。由于多媒体定时器另外开辟一个独立线程执行定时器回调函数,因此当回调函数耗时较多时并不会导致UI的“假死”,相对而言,Win32定时器隶属于主线程,一旦定时器回调函数耗时较多,就会导致UI的“假死”。

    多媒体定时器相关API如下:

    MMRESULT timeGetDevCaps(

        LPTIMECAPS ptc,

        UINT cbtc      

        );

    函数功能:获取定时器设备能力

    参数:ptc指向一个TIMECAPS型的结构,TIMECAPS有两个成员,wPeriodMin和WperiodMax,表示定时器设备支持的最小时间周期和最大时间周期;cbtc表示TIMECAPS结构的大小

    MMRESULT timeBeginPeriod(

        UINT uPeriod

        );

    函数功能:设置定时器设备的最小时间分辨率

    参数:最小时间分辨率,以毫秒为单位

    MMRESULT timeEndPeriod(

        UINT uPeriod

        );

    函数功能:清除之前对定时器设备的设置

    参数:timeBeginPeriod中指定的最小分辨率

    注:timeBeginPeriod和timeEndPeriod必须配对存在,并且指定的参数值也相同。

    MMRESULT timeSetEvent(

        UINT           uDelay,    

        UINT           uResolution,

        LPTIMECALLBACK lpTimeProc,

        DWORD_PTR      dwUser,    

        UINT           fuEvent    

        );

    函数功能:创建并初始化定时器事件,给定定时器回调函数的入口地址

    参数:

    uDelay:定时器触发的时间间隔,以毫秒为单位

    uResolution:定时器设备的时间精度,以毫秒为单位,应大于或等于timeBeginPeriod中设置的值,默认为1ms,精度越高,系统在定时器上的负载就越大,通常选择适合于应用程序的最大值

    LpTimeProc:定时器触发的事件的回调函数的地址

    dwUser:传递给回调函数的数据

    fuEvent:定时类型,TIME_ONESHOT表示uDelay毫秒后只产生一次事件,TIME_PERIODIC表示每隔uDelay毫秒周期性的产生事件

    MMRESULT timeKillEvent(

        UINT uTimerID

        );

    函数功能:删除一个指定的定时器事件

    参数:指向要删除的定时器事件的ID

    void CALLBACK TimeProc(UINT uID,UINT uMsg,DWORD dwUsers,DWORD dw1,DWORD dw2);

    函数功能:回调函数

    参数: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);
    };
    View Code

     

    #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;
        }
    }
    View Code

     

     为了对比多媒体定时器与Win32定时器的精度,我们实现了一个测试软件,即定时器指定一个时间间隔(单位:ms),再测试出真实的时间间隔(采用高精度计时函数QueryPerformanceFrequencyQueryPerformanceCounter),下面是测试结果,可以发现win32定时器设定1ms间隔,但实际时间间隔为15.6ms,而多媒体定时器设定1ms,真实的时间间隔就是1ms

    三、相关问题

    (1)如何在回调函数中调用类内成员

    void CALLBACK TimeProc(UINT uID,UINT uMsg,DWORD dwUsers,DWORD dw1,DWORD dw2);

    可以将TimeProc声明为全局函数,但通常会将其放入类中,如本程序就将其放入MMTimer类中。此时需要将其声明为static类型(因为非静态成员函数的指针与静态成员函数的指针是不同的,主要是由于非静态成员函数的参数中隐含一个this指针,导致函数指针的类型不匹配)。

    然而将TimeProc声明为static,它只能访问类中的静态成员(变量与函数),这里的解决方法是利用参数dwUsers,该参数对应timeSetEvent中的dwUser,我们将自定义类MMTimethis指针作为参数传进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

     

  • 相关阅读:
    Mac部署hadoop3.2.1(伪分布式) ,Hadoop自带的MapReduce程序(wordcount),,,,安装scala,hadoop安装启动问题,Pyspark开发环境搭建,MAC Spark安装和环境变量设置
    使用objdump objcopy查看与修改符号表
    alias, bg, bind, break, builtin, caller, cd, command,
    virtualbox端口转发
    CMake快速入门教程-实战
    内存管理
    http调试工具,linux调试工具
    CSS Background
    RadioButton的check改变的时候
    Docs-->.NET-->API reference-->System.​Web.​UI.​Web​Controls-->Repeater
  • 原文地址:https://www.cnblogs.com/luo-peng/p/5782955.html
Copyright © 2020-2023  润新知