MFC 視窗類別 (CWnd) 內建 SetTimer/OnTimer 機制,但普通 C++ 類別無 HWND 且無訊息對應表,無法直接使用。解決方案:Win32 定時器回呼函數 + 靜態成員 + CMap 映射表。
this 指標VOID CALLBACK TimerProc(HWND, UINT, UINT_PTR, DWORD)this 指標,僅能存取靜態成員,但可透過參數傳入物件指標
template<class KEY, class ARG_KEY, class VALUE, class ARG_VALUE>
class CMap : public CObject
// 快速鍵值對查找 (雜湊表),適合「定時器 ID → 物件指標」映射
class CPerson {
CString m_motto;
CPerson* m_pThis; // 儲存 this
public:
CPerson() { m_pThis = this; }
void ShowMotto() { AfxMessageBox(m_motto); }
static void StaticShow(CPerson* p) { p->ShowMotto(); } // 透過參數傳遞 this
};
// 使用
person.StaticShow(person.m_pThis);
#pragma once
#include <afxtempl.h>
class CMyTimer;
typedef CMap<UINT, UINT, CMyTimer*, CMyTimer*> CTimerMap;
class CMyTimer {
public:
CMyTimer();
virtual ~CMyTimer();
// 設定定時器 (毫秒間隔, 提示文字)
void SetMyTimer(UINT nElapse, CString sz);
void KillMyTimer();
protected:
UINT m_nTimerID = 0;
CString m_szContent;
// 靜態成員:全類別共享
static CTimerMap m_sTimerMap; // ID → this 映射
static VOID CALLBACK TimerProc(HWND, UINT, UINT_PTR idEvent, DWORD);
};
#include "stdafx.h"
#include "MyTimer.h"
// 必須在外部定義靜態成員
CTimerMap CMyTimer::m_sTimerMap;
CMyTimer::CMyTimer() {}
CMyTimer::~CMyTimer() { KillMyTimer(); }
VOID CALLBACK CMyTimer::TimerProc(HWND, UINT, UINT_PTR idEvent, DWORD) {
CMyTimer* pThis = NULL;
if (m_sTimerMap.Lookup(idEvent, pThis) && pThis) {
CString msg;
msg.Format(_T("%u 號定時器: %s"), idEvent, pThis->m_szContent);
AfxMessageBox(msg);
}
}
void CMyTimer::SetMyTimer(UINT nElapse, CString sz) {
m_szContent = sz;
// NULL hWnd, NULL ID (系統自動分配), 回呼函數
m_nTimerID = SetTimer(NULL, 0, nElapse, TimerProc);
if (m_nTimerID)
m_sTimerMap[m_nTimerID] = this; // 登記映射
}
void CMyTimer::KillMyTimer() {
if (m_nTimerID) {
KillTimer(NULL, m_nTimerID);
m_sTimerMap.RemoveKey(m_nTimerID);
m_nTimerID = 0;
}
}
CMyTimer timer1, timer2, timer3;
timer1.SetMyTimer(1000, _T("每秒提醒:喝水"));
timer2.SetMyTimer(5000, _T("每5秒:伸展運動"));
timer3.SetMyTimer(30000, _T("每30秒:休息眼睛"));
// 程式結束前
timer1.KillMyTimer();
timer2.KillMyTimer();
timer3.KillMyTimer();
| 議題 | 現狀 | 改進建議 |
|---|---|---|
| 執行緒安全 | 單執行緒 UI 應用足夠 | 多執行緒環境需 CCriticalSection 保護 Map |
| 精確度 | ~10-15ms (系統定時器精度) | 高精度需 CreateWaitableTimer + 執行緒池 |
| 記憶體洩漏 | 析構函數自動 Kill | 搭配智慧指標 shared_ptr 管理生命週期 |
| 參數傳遞 | 靜態字串成員 | 使用 std::function + Lambda 捕獲上下文 |
靜態回呼函數配合 CMap 映射表,優雅解決了非視窗類別使用系統定時器的難題。此模式可延伸至任何需「全域回呼關聯具體物件」的場景 (如 Hook 程序、非同步 I/O 完成通知)。掌握靜態成員特性與映射表應用,是 C++ 系統程式設計的核心技能。