有時候我們需要用到定時器這樣一個東西,但是我們如果去一個窗口裏面 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 的窗口動畫類的實現。