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 的窗口動畫類的實現。





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章