首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> VC >> 關於VC++

非視窗類別使用定時器:靜態成員函數回呼與 CMap 管理機制完整解析

     編輯:關於VC++

核心挑戰

MFC 視窗類別 (CWnd) 內建 SetTimer/OnTimer 機制,但普通 C++ 類別無 HWND 且無訊息對應表,無法直接使用。解決方案:Win32 定時器回呼函數 + 靜態成員 + CMap 映射表。

關鍵知識點回顧

1. 回呼函數限制

  • 必須是 全域函數 或 靜態成員函數
  • 無法存取 this 指標
  • 簽名固定:VOID CALLBACK TimerProc(HWND, UINT, UINT_PTR, DWORD)

2. C++ 靜態成員特性

  • 靜態資料成員:全類別共享單一拷貝,類別作用域,不佔用物件大小
  • 靜態成員函數:無 this 指標,僅能存取靜態成員,但可透過參數傳入物件指標
  • 定義於類別內宣告,必須在 .cpp 中定義一次

3. CMap 模板類別


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);

完整定時器類別實現

MyTimer.h


#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);
};

MyTimer.cpp


#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 捕獲上下文

應用場景

  • 網路連線逾時偵測
  • 定時資料同步/備份
  • 非 UI 背景工作排程
  • 嵌入式系統週期性掃描

總結

靜態回呼函數配合 CMap 映射表,優雅解決了非視窗類別使用系統定時器的難題。此模式可延伸至任何需「全域回呼關聯具體物件」的場景 (如 Hook 程序、非同步 I/O 完成通知)。掌握靜態成員特性與映射表應用,是 C++ 系統程式設計的核心技能。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved