C++工具箱——定时器

Posted 牧秦丶

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++工具箱——定时器相关的知识,希望对你有一定的参考价值。

有时候我们需要用到定时器这样一个东西,但是我们如果去一个窗口里面 SetTimer,但我们又需要在一个非 UI 类(线程)里要用计时器,那么解耦就没有办法实现了。有没有更好的办法呢?


答案是肯定的,我看可以写一个单件定时器类,用来管理定时控制,并且全局访问。你可能需要的知识有:单件模板类Boost 等。我们期望的使用方式是:

/**
 * \\file	timer.h
 * \\author	arnozhang
 * \\date	2012.8.13
 * \\brief	系统计时器模块.
 */

#ifndef	__TIMER_H__
#define	__TIMER_H__


namespace Util



//
// 计时器回调函数.
//
typedef boost::function<void()> TimerCallback;
typedef boost::function<void()> AsyncCallProc;


/**
 * 设置并启动一个计时器.
 *
 * \\param[in]	timerID:	计时器 ID.
 * \\param[in]   nElapse:    计时器响应间隔.
 * \\param[in]   timerCbk:   计时器回调.
 * \\param[in]   bLoopTimer: 是否是循环计时器.
 *
 * \\remarks
 *	当 bLoopTimer 为 false 时,计时器的回调函数只执行一次.
 *  然后该计时器会从计时器管理器中删除.
 */
void SetTimerCallback(
    void* timerID,
    int nElapse,
    TimerCallback timerCbk,
    bool bLoopTimer = true
    );


/**
 * 清除一个计时器.
 *
 * \\param[in]	timerID:	清除的计时器的 ID.
 *
 * \\remarks
 *  如果计时器管理器中没有这个 ID 对应的计时器,
 *  将什么都不做.否则删除计时器.
 */
void KillTimerCallback(void* timerID);


inline void AsyncCall(AsyncCallProc asyncProc)

    int dummy = 0;
    SetTimerCallback(&dummy, 500, asyncProc, false);



#define ASYNC_CALL_BIND(class_name, method_name) \\
    boost::bind(&class_name::method_name, this)


#define TIMER_CALL_BIND(class_name, method_name) \\
    ASYNC_CALL_BIND(class_name, method_name)



 /*namespace Util ends here.*/


#endif /*__TIMER_H__*/


为什么我们要将 timerID 用 void* 表示呢?由于当定时器设置过多时,这个 ID 有可能重复,所以我们用一片内存的首地址来表示,尽量减小定时器的 ID 重复的可能性。我们还间接通过定时器实现了一个异步调用 AsyncCall 


那么如何实现呢?首先,我们考虑到:

1、Timer 管理器要全局唯一,便于管理——用单件

2、暴露的接口只有上述——实现全部放入 timer.cpp ;

3、计时器的 ID要尽量唯一,减小重复——考虑用回调类的this指针或者临时对象的地址

4、任何类、线程均可使用该 Timer

5、Timer 回调要简单易用——Boost::function 解决


考虑到上述需求,我们在 timer.cpp 中的一个匿名命名空间(思考为什么不在Util 中)中定义一个单件类 CTimerMgr,并尽数实现 timer.h 中的类即可。走起:

#include "stdafx.h"
#include "timer.h"
#include "Singleton.h"
using namespace Util;


namespace



//
// 计时器管理器类.模块外不可见.
//
class CTimerMgr : public Singleton<CTimerMgr>

public:
    struct TIME_ITEM
    
    public:
        void*           timerID;
        int             nElapse;
        TimerCallback   timerCbk;
        bool            bLoop;
    ;

    typedef map<void*, TIME_ITEM> TimerMap;

    CTimerMgr()
    
        _InitTimerMgr();
    

    ~CTimerMgr()
    
        _UninitTimerMgr();
    


    void SetTimerCallback(
        void* timerID,
        int nElapse,
        TimerCallback timerCbk,
        bool bLoopTimer
        )
    
        TIME_ITEM newItem =
        
            timerID, nElapse, timerCbk, bLoopTimer
        ;
        m_timers[timerID] = newItem;

        ::SetTimer(m_hTimerWnd, (UINT_PTR)timerID, nElapse, NULL);
    

    void KillTimerCallback(void* timerID)
    
        TimerMap::iterator iter = m_timers.find(timerID);
        if (iter != m_timers.end())
        
            ::KillTimer(m_hTimerWnd, (UINT_PTR)timerID);
            m_timers.erase(iter);
        
    


private:
    void OnTimer(void* timerID)
    
        TimerMap::iterator iter = m_timers.find(timerID);
        if (iter != m_timers.end())
        
            TIME_ITEM& item = iter->second;
            TimerCallback cbk = item.timerCbk;

            // 非循环Timer,删除之.
            if (!item.bLoop)
            
                ::KillTimer(m_hTimerWnd, (UINT_PTR)timerID);
                m_timers.erase(iter);
            

            cbk();
        
    

    static HRESULT CALLBACK OnTimerWndProc(
        HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam
        )
    
        switch (uMsg)
        
        case WM_TIMER:
            
                void* timerID = reinterpret_cast<void*>(wParam);
                CTimerMgr::GetInstance().OnTimer(timerID);
            
            break;

        default:
            break;
        

        return ::DefWindowProcW(hWnd, uMsg, wParam, lParam);
    

    void _InitTimerMgr()
    
        const WCHAR* const TIMER_CLS_NAME = L"__timer_cls_name";
        const WCHAR* const TIMER_WND_NAME = L"__timer_wnd_name";

        WNDCLASSEXW wndcls      = sizeof(wndcls);
        wndcls.hInstance        = ::GetModuleHandle(NULL);
        wndcls.lpszClassName    = TIMER_CLS_NAME;
        wndcls.hbrBackground    = (HBRUSH)::GetStockObject(NULL_BRUSH);
        wndcls.lpfnWndProc      = &CTimerMgr::OnTimerWndProc;
        wndcls.style            = CS_HREDRAW | CS_VREDRAW;

        ::RegisterClassExW(&wndcls);

        m_hTimerWnd = ::CreateWindowExW(
            0,
            TIMER_CLS_NAME,
            TIMER_WND_NAME,
            WS_OVERLAPPED,
            0, 0, 0, 0,
            HWND_MESSAGE,
            NULL,
            wndcls.hInstance,
            NULL
            );
    

    void _UninitTimerMgr()
    
        for (TimerMap::iterator iter = m_timers.begin();
            iter != m_timers.end();
            ++iter
            )
        
            ::KillTimer(m_hTimerWnd, (UINT_PTR)iter->first);
        

        m_timers.clear();
    


private:
    TimerMap    m_timers;
    HWND        m_hTimerWnd;
;


 /*namespace anonymous ends here.*/


void Util::SetTimerCallback(
    void* timerID,
    int nElapse,
    TimerCallback timerCbk,
    bool bLoopTimer /* = true */
    )

    CTimerMgr::GetInstance().SetTimerCallback(
        timerID,
        nElapse,
        timerCbk,
        bLoopTimer
        );


void Util::KillTimerCallback(void* timerID)

    CTimerMgr::GetInstance().KillTimerCallback(timerID);


我们在 CTimerMgr 内部维护了一个定时器信息的 map。该 map 用定时器的 ID 做键值。在 CTimerMgr 内实现了一个隐藏在内部的Windows 窗口,所有的计时器消息将送往该窗口的 WM_TIMER 处理函数。看起来没有一点难度。但功能非常强大:

#include "timer.h"


class CMyA

public:
	CMyA() : m_value(0)
	
	
	
	void f()
	
		::SetTimerCallback(this, 1000, TIMER_CALL_BIND(CMyA, _timer_proc));
	
	
private:
	void _timer_proc()
	
		cout<<m_value++<<endl;
	
	
	int m_value;
;


void _global_proc()

	static int val = 0;
	cout<<val++<<endl;



int WinMain(HINSTANCE, HINSTANCE, LPCTSTR, int)

	// ...
	int _dummy;
	::SetTimerCallback(&_dummy, 1000, _global_proc);

	// ...
	return 0;

通过这个计时器,我们可以实现其他一些强大的功能,比如窗口动画、事件管理等等。下一章将讲解基于该 Timer 的窗口动画类的实现。





以上是关于C++工具箱——定时器的主要内容,如果未能解决你的问题,请参考以下文章

C++ 跨平台高分辨率计时器

C++工具箱——动画类之透明度渐变 & 大小渐变

C++学习(三六三)编译Android平台的iconv

打开文件并将数据放入双向量 C++

C++怎么用GRAPHICS.H?

为啥我无法将对象 push_back 放入 C++ 多维向量中