小步慢跑

 

SonicUI 内部定时器实现分析

SonicUI中有一个内部定时器的概念(InternalTimer),SonicUI中的动画效果都是使用的这个定时器。这个定时器实现的思路是很清晰的:WM_TIMER消息加定时轮询。

首先使用SonicUI的工程都有一个全局的CSonicUI类的实例。在这个类中有一个静态的成员变量HWND m_hWnd,它指向的是一个 "SonicWnd"的窗口类的窗口实例。此窗口类在CSoinicUI::Init中定义如下:

   1:  
   2: #define MY_WND            _T("SonicWnd")
   3: BOOL CSonicUI::Init()
   4:  
   5: {
   6:     WNDCLASSEX wcex;
   7:     wcex.cbSize = sizeof(WNDCLASSEX);
   8:     wcex.style            = CS_HREDRAW | CS_VREDRAW;
   9:     wcex.lpfnWndProc    = (WNDPROC)CSonicUI::InternalWndProc;
  10:     wcex.cbClsExtra        = 0;
  11:     wcex.cbWndExtra        = 0;
  12:     wcex.hInstance        = NULL;
  13:     wcex.hIcon            = NULL;
  14:     wcex.hCursor        = LoadCursor(NULL, IDC_ARROW);
  15:     wcex.hbrBackground    = NULL;
  16:     wcex.lpszMenuName    = NULL;
  17:     wcex.lpszClassName    = MY_WND;
  18:     wcex.hIconSm        = NULL;
  19:     if(!RegisterClassEx(&wcex))
  20:     {
  21:         return FALSE;
  22:     }
  23:     HMODULE hMod = GetModuleHandle(_T("User32.dll"));
  24:     if(hMod == NULL)
  25:     {
  26:         return FALSE;
  27:     }
  28:     m_pOldBeginPaint = ReplaceFuncAndCopy(GetProcAddress(hMod, "BeginPaint"), MyBeginPaint);
  29:     m_pOldEndPaint = ReplaceFuncAndCopy(GetProcAddress(hMod, "EndPaint"), MyEndPaint);
  30:     if(m_pOldEndPaint == NULL || m_pOldEndPaint == NULL)
  31:     {
  32:         return FALSE;
  33:     }
  34:     return TRUE;
  35: }

 

这个窗口始终是不可见的,但SonicUI通过他来实现内部消息的转发,定时器消息就是在 这个窗口类的消息处理函数(InternalWndProc)中处理的。m_hWnd成员变量是在CSonicUI::GetSonicUI ()中被赋值的。代码如下:

   1: ISonicUI * GetSonicUI()
   2: {
   3:     BOOL bRet = FALSE;
   4:     if(CSonicUI::m_hWnd == NULL)
   5:     {
   6:         // Initialization
   7:         __try
   8:         {            
   9:             // 省略
  10:         }
  11:         __finally
  12:         {
  13:             if(bRet)
  14:             {
  15:                 //创建了全局唯一的不可见窗口,用于转发内部消息
  16:                 CSonicUI::m_hWnd = CreateWindow(MY_WND, NULL, WS_POPUP, 0, 0, 1, 1, NULL, NULL, NULL, NULL);
  17:                 g_UI.CreateTip();
  18:                 
  19:                 //开启了一个 10ms定时器,相当于每隔10ms就轮询下当前是否有定时器到期
  20:                 SetTimer(CSonicUI::m_hWnd, TIMER_BASE_DATA, GIF_INTERVAL, NULL);
  21:             }
  22:         }
  23:     }
  24:     else
  25:     {
  26:         bRet = TRUE;
  27:     }
  28:     if(!bRet)
  29:     {
  30:         return NULL;
  31:     }
  32:     return &g_UI;
  33: }

可见,当SonicUI第一次初始化后就开始了一个10ms间隔的定时器。那这个定时器如何使用呢?看一下设置、删除定时器的代码。在 ISonicBaseData 类中。代码如下:

   1: typedef list<ISonicBaseData *> LIST_BASE_DATA;
   2: class ISonicBaseData
   3: {
   4: public:
   5:     //删除了和定时器不相关的代码
   6:  
   7:     typedef map<int, DWORD> INTERVAL_TO_TIMER; //定时器间隔和定时器id的map,注意一个定期是间隔可能关联着多个定时器ID
   8:  
   9:     void   OnInternalTimer();//全局的窗口的的轮询周期(10ms)到达如果当前类中设置了定时器,此方法会被调用
  10:     virtual void SetInternalTimer(DWORD dwTimerId, int nInterval, BOOL bOnceTimer = FALSE);//设置定时器,注意 dwTimerId 的定义
  11:     virtual void KillInternalTimer(DWORD dwTimerId);
  12:     virtual BOOL QueryInternalTimer(DWORD dwTimerId);//查看当前是否设置了dwTimerId的定时器
  13:     virtual void ClearInternalTimer();//删除所有的定时器
  14:     virtual void OnInternalTimer(DWORD dwTimerId); //dwTimerId 对应的定时周期到达
  15:  
  16:     DWORD m_dwTimer;      // 从第一次设置定时器起到现在止一共有多少毫秒
  17:     DWORD m_dwTimerOnce; //保存所有一次性定时器的定时器ID
  18:     DWORD  m_dwTimerId;      // 保存当前的所有定时器ID,使用 位掩码
  19:     INTERVAL_TO_TIMER m_mapIntervalToTimer;
  20:     HWND m_hWnd;    
  21:     static LIST_BASE_DATA m_TimerList; //静态成员,类似全局变量的作用,保存了当前所有设置了定时器的 ISonicBaseData 的实例
  22: };

由于 ISonicBaseData 是SonicUI中所有控件的基类,这意味这SonicUI所有的控件都支持内部定时器。目前我们看到了两个全局变量(类似于全局变量):可以每10ms产生一个wm_timer消息的 sonicui对象和记录的所有申请了定时器的控件对象(ISonicBaseData 的子类)。SonicUI的定时器机制是这样的:每一个轮询周期(10ms)到达,sonicui对象 就问每一个ISonicBaseData 对象,“又过去10ms了,你有没有定时器到期啊?”,ISonicBaseData  就看自己内部申请的定时器中有没有到期的,如果有的话,他就执行这个定时器(调用OnInternalTimer(DWORD dwTimerId))。

接下来要了解的有两个问题:

  • ISonicBaseData怎么维护的定时器ID的?SonicUI的内部定时器的ID定义如下:
   1:  
   2: #define TIMER_SHOWING_GENTLY       0x1
   3: #define TIMER_MOVE_GENTLY          0x2
   4: #define TIMER_FRAME                0x8    
   5: #define TIMER_TRANSFORM            0x10
   6: #define TIMER_SLIDE                0x20
   7: #define TIMER_TRACK_MOUSE          0x40
   8: #define TIMER_SHUTTER              0x80

不难看出这是win32api中常用的“按位设置值”(这个不知道怎么说,掩码?)的定义方法。可以使用一个DWORD来表示多个定时器的ID,对定时器ID的增删查就可以用下面的语句完成:

   1: //定义了一个定时器
   2: DWORD dwTimerID = TIMER_SHOWING_GENTLY;
   3: //增加一个定时器
   4: dwTimerID  |= TIMER_MOVE_GENTLY;
   5: //删除一个定时器
   6: dwTimerID   &= ~TIMER_MOVE_GENTLY;
   7: //查询一个定时器是否存在
   8: BOOL bExist = dwTimerID&TIMER_MOVE_GENTLY;
  •   如何判断一个定时器是否到期?

    这个问题的答案就是 ISonicBaseData的m_dwTimer变量。它记录了从上次 增加定时器到现在的总时长(单位毫秒)。每当调用SetInternalTimer是就把他清零(这个会影响当前已经设置了的定时器),当全局的轮询周期到达就把m_dwTimer加10毫秒,然后看m_dwTimer是内部哪个定时器的周期的整倍数,是就代表这个定时器到期了,否则就是没到期,等下一个轮询周期的到来。因此m_dwTimer的取值总是10的倍数,而内部定时器的周期也必须是10的倍数。SonicUI中几种内部定时器的周期定义如下:

   1: // internal timer defined must be times of base interval
   2: #define BASE_INTERVAL            10
   3: #define ANIMATION_INTERVAL        20
   4: #define GIF_INTERVAL            10
   5: #define FADEOUT_INTERVAL        50

 

可见,SonicUI的定时器精度并不高,但相比较 CreateTimerQueueTimer 避免了多线程。

posted on 2012-08-29 17:42 zaccheo 阅读(617) 评论(0)  编辑 收藏 引用 所属分类: C++ win32/MFC


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理


导航

统计

常用链接

留言簿

随笔分类(23)

随笔档案(26)

文章分类(1)

文章档案(1)

csdn

搜索

最新评论

阅读排行榜

评论排行榜