目标:游戏编程中经常需要使用定时器,多个定时器往往同时在运行,所以希望有一个独立的类,专门管理定时器。并支持console模式,因为服务器很有可能以console模式运行。客户端可以方便地使用定时器,虽然SetTimer函数的第四个参数可以设置回调函数,但这样每个使用的客户都必须实现一个静态或全局的函数作为回调函数,使用起来很不方便。
分析:console模式没有类似窗口模式的GetMessage函数,不能获取WM_TIMER消息。经过研究发现Console模式下也可以使用GetMessage函数,于是决定开一个线程专门用来GetMessage,当客户代码需要设定定时器时,直接使用SetTimer函数设定定时器。通过试验发现这么做GetMessage不能获得WM_TIMER消息。因为SetTimer的线程与GetMessage不是同一个线程。使用PostThreadMessage给等待在GetMessage的线程发自定义消息,让等待在GetMessage消息的线程解析PostThreadMessage参数,然后设置或取消定时器,这样,设定与获取定时器的线程就是同一个的了。
以下是代码:
/* ITimer.h 所有希望使用定时器的客户必须继承ITimer接口,实现OnTimerEnd函数,当定时器触发时会调用该函数 */
#ifndef _ITIMER_H__
#define _ITIMER_H__
class ITimer
{
public:
virtual void OnTimeEnd(int iTimerId) = 0;
virtual ~ITimer() {};
};
#endif /* _ITIMER_H__ */
/* ZTimer.h 定时器管理器类的头文件,两个公共方法StartTimer和EndTimer,相信不说也知道作用了 */
#ifndef _ZTIMER_H__
#define _ZTIMER_H__
#include <map>
using namespace std;
#include "Mutex.h"
#include "ITimer.h"
class ZTimer
{
public:
ZTimer();
int StartTimer(int iMilliSec, ITimer *pITimer);
void EndTimer(int iTimer);
protected:
void CreateSchedulerThread();
static void _stdcall TimerProc(HWND hwnd, UINT uMsg, UINT idEvent, DWORD dwTime);
static DWORD _stdcall SchedulerThread(LPVOID pVoid);
private:
ZMutex m_mutex; // 用于支持多线程,对CRITICAL_SECTION的简单封装
DWORD m_dwThreadId; // 阻塞在GetMessage函数的线程ID,在PostThreadMessage中使用
map<int, ITimer *> m_mapIdITimer; // 每个TimerId对应的客户,用于定时器发生后调用OnTimeEnd
HANDLE m_hSchedulerRun; // 用于保证GetMessage线程先于调用StartTimer的线程运行,不知是否还有更好的方法
HANDLE m_hEventGetTimerId; // 得到并返回TimerId
int m_iTimerId;
};
#endif /* _ZTIMER_H__ */
/* ZTimer.cpp ZTimer.h的具体实现 */
#include <windows.h>
#include "ZTimer.h"
// wparam 为VOTE_ID VOTE_TIME, lparam 为ITimer
#define START_TIMER_MSG WM_USER + 1
// wparam 为timer id值
#define END_TIMER_MSG WM_USER + 2
// 最大等待时间
#define MAX_WAIT_TIME 2000
ZTimer::ZTimer()
{
// 保证子线程先运行
m_hSchedulerRun = CreateEvent(NULL, true, false, NULL);
// 获取Timer ID
m_hEventGetTimerId = CreateEvent(NULL, false, false, NULL);
CreateSchedulerThread();
if (WaitForSingleObject(m_hSchedulerRun, MAX_WAIT_TIME) == WAIT_TIMEOUT)
{
// to do
printf("ztimer error:---------------------------------------------------****\n");
}
}
int ZTimer::StartTimer(int iMilliSec, ITimer *pITimer)
{
m_mutex.Lock();
PostThreadMessage(m_dwThreadId, START_TIMER_MSG, (WPARAM)iMilliSec, (LPARAM)pITimer);
if (WaitForSingleObject(m_hEventGetTimerId, MAX_WAIT_TIME) == WAIT_OBJECT_0)
{
m_mapIdITimer.insert(make_pair(m_iTimerId, pITimer));
m_mutex.Unlock();
return m_iTimerId;
}
else
{
// to do, error
printf("start timer error:---------------------------------------------------****\n");
m_mutex.Unlock();
return 0;
}
}
void ZTimer::EndTimer(int iTimer)
{
m_mutex.Lock();
PostThreadMessage(m_dwThreadId, END_TIMER_MSG, iTimer, 0);
if (WaitForSingleObject(m_hEventGetTimerId, MAX_WAIT_TIME) == WAIT_OBJECT_0)
{
m_mapIdITimer.erase(iTimer);
}
else
{
// to do, error
}
m_mutex.Unlock();
}
void ZTimer::TimerProc(HWND hwnd, UINT uMsg, UINT idEvent, DWORD dwTime)
{
}
void ZTimer::CreateSchedulerThread()
{
CreateThread(NULL, 0, SchedulerThread, (void *)this, 0, &m_dwThreadId);
}
// 调度线程函数,用于阻塞在GetMessage中,处理WM_TIMER消息
DWORD ZTimer::SchedulerThread(LPVOID pVoid)
{
ZTimer *pThis = (ZTimer *)pVoid;
MSG msg;
SetEvent(pThis->m_hSchedulerRun);
while(GetMessage(&msg, NULL, 0, 0))
{
if (msg.message == START_TIMER_MSG)
{
pThis->m_iTimerId = (int)SetTimer(NULL, 0, (UINT)msg.wParam, TimerProc);
SetEvent(pThis->m_hEventGetTimerId);
}
else if (msg.message == END_TIMER_MSG)
{
KillTimer(NULL, msg.wParam);
}
else if (msg.message == WM_TIMER)
{
ITimer *pTimer = pThis->m_mapIdITimer[(int)msg.wParam];
if (pTimer != NULL)
{
pTimer->OnTimeEnd((int)msg.wParam);
}
else
{
printf("wm_timer error\n");
}
}
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
分析:console模式没有类似窗口模式的GetMessage函数,不能获取WM_TIMER消息。经过研究发现Console模式下也可以使用GetMessage函数,于是决定开一个线程专门用来GetMessage,当客户代码需要设定定时器时,直接使用SetTimer函数设定定时器。通过试验发现这么做GetMessage不能获得WM_TIMER消息。因为SetTimer的线程与GetMessage不是同一个线程。使用PostThreadMessage给等待在GetMessage的线程发自定义消息,让等待在GetMessage消息的线程解析PostThreadMessage参数,然后设置或取消定时器,这样,设定与获取定时器的线程就是同一个的了。
以下是代码:
/* ITimer.h 所有希望使用定时器的客户必须继承ITimer接口,实现OnTimerEnd函数,当定时器触发时会调用该函数 */
#ifndef _ITIMER_H__
#define _ITIMER_H__
class ITimer
{
public:
virtual void OnTimeEnd(int iTimerId) = 0;
virtual ~ITimer() {};
};
#endif /* _ITIMER_H__ */
/* ZTimer.h 定时器管理器类的头文件,两个公共方法StartTimer和EndTimer,相信不说也知道作用了 */
#ifndef _ZTIMER_H__
#define _ZTIMER_H__
#include <map>
using namespace std;
#include "Mutex.h"
#include "ITimer.h"
class ZTimer
{
public:
ZTimer();
int StartTimer(int iMilliSec, ITimer *pITimer);
void EndTimer(int iTimer);
protected:
void CreateSchedulerThread();
static void _stdcall TimerProc(HWND hwnd, UINT uMsg, UINT idEvent, DWORD dwTime);
static DWORD _stdcall SchedulerThread(LPVOID pVoid);
private:
ZMutex m_mutex; // 用于支持多线程,对CRITICAL_SECTION的简单封装
DWORD m_dwThreadId; // 阻塞在GetMessage函数的线程ID,在PostThreadMessage中使用
map<int, ITimer *> m_mapIdITimer; // 每个TimerId对应的客户,用于定时器发生后调用OnTimeEnd
HANDLE m_hSchedulerRun; // 用于保证GetMessage线程先于调用StartTimer的线程运行,不知是否还有更好的方法
HANDLE m_hEventGetTimerId; // 得到并返回TimerId
int m_iTimerId;
};
#endif /* _ZTIMER_H__ */
/* ZTimer.cpp ZTimer.h的具体实现 */
#include <windows.h>
#include "ZTimer.h"
// wparam 为VOTE_ID VOTE_TIME, lparam 为ITimer
#define START_TIMER_MSG WM_USER + 1
// wparam 为timer id值
#define END_TIMER_MSG WM_USER + 2
// 最大等待时间
#define MAX_WAIT_TIME 2000
ZTimer::ZTimer()
{
// 保证子线程先运行
m_hSchedulerRun = CreateEvent(NULL, true, false, NULL);
// 获取Timer ID
m_hEventGetTimerId = CreateEvent(NULL, false, false, NULL);
CreateSchedulerThread();
if (WaitForSingleObject(m_hSchedulerRun, MAX_WAIT_TIME) == WAIT_TIMEOUT)
{
// to do
printf("ztimer error:---------------------------------------------------****\n");
}
}
int ZTimer::StartTimer(int iMilliSec, ITimer *pITimer)
{
m_mutex.Lock();
PostThreadMessage(m_dwThreadId, START_TIMER_MSG, (WPARAM)iMilliSec, (LPARAM)pITimer);
if (WaitForSingleObject(m_hEventGetTimerId, MAX_WAIT_TIME) == WAIT_OBJECT_0)
{
m_mapIdITimer.insert(make_pair(m_iTimerId, pITimer));
m_mutex.Unlock();
return m_iTimerId;
}
else
{
// to do, error
printf("start timer error:---------------------------------------------------****\n");
m_mutex.Unlock();
return 0;
}
}
void ZTimer::EndTimer(int iTimer)
{
m_mutex.Lock();
PostThreadMessage(m_dwThreadId, END_TIMER_MSG, iTimer, 0);
if (WaitForSingleObject(m_hEventGetTimerId, MAX_WAIT_TIME) == WAIT_OBJECT_0)
{
m_mapIdITimer.erase(iTimer);
}
else
{
// to do, error
}
m_mutex.Unlock();
}
void ZTimer::TimerProc(HWND hwnd, UINT uMsg, UINT idEvent, DWORD dwTime)
{
}
void ZTimer::CreateSchedulerThread()
{
CreateThread(NULL, 0, SchedulerThread, (void *)this, 0, &m_dwThreadId);
}
// 调度线程函数,用于阻塞在GetMessage中,处理WM_TIMER消息
DWORD ZTimer::SchedulerThread(LPVOID pVoid)
{
ZTimer *pThis = (ZTimer *)pVoid;
MSG msg;
SetEvent(pThis->m_hSchedulerRun);
while(GetMessage(&msg, NULL, 0, 0))
{
if (msg.message == START_TIMER_MSG)
{
pThis->m_iTimerId = (int)SetTimer(NULL, 0, (UINT)msg.wParam, TimerProc);
SetEvent(pThis->m_hEventGetTimerId);
}
else if (msg.message == END_TIMER_MSG)
{
KillTimer(NULL, msg.wParam);
}
else if (msg.message == WM_TIMER)
{
ITimer *pTimer = pThis->m_mapIdITimer[(int)msg.wParam];
if (pTimer != NULL)
{
pTimer->OnTimeEnd((int)msg.wParam);
}
else
{
printf("wm_timer error\n");
}
}
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}