上一篇:MiniGUI源码分析--hellowworld(2):主窗口诞生的秘密
这本篇中,将重点介绍MiniGUI的消息。
在MiniGUI中,有以下几种消息:
1. 同步消息,由SendMessage、SendSyncMessage发送的同步消息,消息将被立即处理,消息的返回值将通过函数的返回值返回;
2. Notify消息,通过SendNotifyMessage发送 。这是一个异步的消息,但是一定能够被处理
3. Post消息,通过PostMessage发送,这也是一个异步消息,可能会丢失
4. 特殊的消息,包括MSG_PAINT和MSG_TIMER,MSG_QUIT,这三个消息是系统消息,在消息队列中,实际上以标志位的方式存在。MSG_PAINT消息可以由InvalidateRect和 UpdateRect两个函数引起。而MSG_TIMER消息,则必须由系统产生。MSG_QUIT只能通过PostQuitMessage产生
消息的优先级是这样定义的:MSG_QUIT > 同步消息 > Notify消息 > Post消息 > MSG_PAINT > MSG_TIMER消息。
在上一篇中,在创建一个窗口时,我们使用了函数 InitMsgQueueThisThread(3.0版本的为mg_InitMsgQueueThisThread)和GetMsgQueueThisThread,前者为当前线程创建一个消息队列(仅限线程版,进程版和standalone版都只有一个消息队列),后者是获取当前线程的消息队列。
InitMsgQueueThisThread函数本身很简单。它主要是创建和初始化MSGQUEUE结构体。(_LITE_VERSION代表的是进程版和standalone版。在3.0中,这个宏已经被规范为_MGRM_PROCESSES 和_MGRM_STANDALONE。)
struct _MSGQUEUE { DWORD dwState; // message queue states #ifndef _LITE_VERSION pthread_mutex_t lock; // lock sem_t wait; // the semaphore for wait message sem_t sync_msg; // the semaphore for sync message #endif PQMSG pFirstNotifyMsg; // head of the notify message queue PQMSG pLastNotifyMsg; // tail of the notify message queue #ifndef _LITE_VERSION PSYNCMSG pFirstSyncMsg; // head of the sync message queue PSYNCMSG pLastSyncMsg; // tail of the sync message queue #else IDLEHANDLER OnIdle; // Idle handler #endif #ifndef _LITE_VERSION PMAINWIN pRootMainWin; // The root main window of this message queue. #endif MSG* msg; /* post message buffer */ int len; /* buffer len */ int readpos, writepos; /* positions for reading and writing */ int FirstTimerSlot; /* the first timer slot to be checked */ DWORD TimerMask; /* timer slots mask */ int loop_depth; /* message loop depth, for dialog boxes. */ };
#define QS_NOTIFYMSG 0x10000000 #ifndef _LITE_VERSION #define QS_SYNCMSG 0x20000000 #else #define QS_DESKTIMER 0x20000000 #endif #define QS_POSTMSG 0x40000000 #define QS_QUIT 0x80000000 #define QS_INPUT 0x01000000 #define QS_PAINT 0x02000000 #define QS_TIMER 0x0000FFFF #define QS_EMPTY 0x00000000
QS_NOTIFYMSG 标志表示消息队列中有待处理的notify消息。同理,QS_SYNCMSG表示有待处理的同步消息;QS_POSTMSG表示有待处理的post消息。
QS_QUIT、QS_PAINT和QS_TIMER分别对应MSG_QUIT, MSG_PAINT和MSG_TIMER。
同步消息在线程版,分成两种情况来实现:1)如果消息发送者和消息接受者在同一个线程,则直接调用窗口的窗口过程;2)如果不在同一个线程,则使用同步消息,来传递。
在非线程版,则统一使用第一种方式。
所以,在线程版的MSGQUEUE结构体中,增加了sync_msg, pFirstSyncMsg和pLastSyncMsg。其中,wait变量的作用是为了唤醒消息循环。
请看SendMessage代码
int GUIAPI SendMessage (HWND hWnd, int iMsg, WPARAM wParam, LPARAM lParam) { WNDPROC WndProc; MG_CHECK_RET (MG_IS_WINDOW(hWnd), -1); #ifndef _LITE_VERSION if (!BE_THIS_THREAD(hWnd)) return SendSyncMessage (hWnd, iMsg, wParam, lParam); #endif /* !_LITE_VERSION */ if ( !(WndProc = GetWndProc(hWnd)) ) return ERR_INV_HWND; return (*WndProc)(hWnd, iMsg, wParam, lParam);BE_IS_THREAD宏通过hWnd的住窗口内保持的线程句柄来判断的。
当不是一个线程时,就调用SendSyncMessage。这个函数
int SendSyncMessage (HWND hWnd, int msg, WPARAM wParam, LPARAM lParam) { PMSGQUEUE pMsgQueue, thinfo = NULL; SYNCMSG SyncMsg; sem_t sync_msg; if (!(pMsgQueue = GetMsgQueue(hWnd))) return ERR_INV_HWND; if ((thinfo = GetMsgQueueThisThread ())) { /* avoid to create a new semaphore object */ SyncMsg.sem_handle = &thinfo->sync_msg; } else { /* this is not a GUI thread */ sem_init (&sync_msg, 0, 0); SyncMsg.sem_handle = &sync_msg; } /* queue the sync message. */ SyncMsg.Msg.hwnd = hWnd; SyncMsg.Msg.message = msg; SyncMsg.Msg.wParam = wParam; SyncMsg.Msg.lParam = lParam; SyncMsg.retval = ERR_MSG_CANCELED; SyncMsg.pNext = NULL; LOCK_MSGQ (pMsgQueue); if (pMsgQueue->pFirstSyncMsg == NULL) { pMsgQueue->pFirstSyncMsg = pMsgQueue->pLastSyncMsg = &SyncMsg; } else { pMsgQueue->pLastSyncMsg->pNext = &SyncMsg; pMsgQueue->pLastSyncMsg = &SyncMsg; } pMsgQueue->dwState |= QS_SYNCMSG; UNLOCK_MSGQ (pMsgQueue); POST_MSGQ (pMsgQueue); /* suspend until the message has been handled. */ if (sem_wait (SyncMsg.sem_handle) < 0) { fprintf (stderr, "SendSyncMessage: thread is interrupted abnormally!\n"); } if (thinfo == NULL) sem_destroy (&sync_msg); return SyncMsg.retval; }
当把消息添加到队里中后,通过POST_MSGQ宏,唤醒消息循环,该宏的定义是:
#define POST_MSGQ(pMsgQueue) \ { \ int sem_value; \ /* Signal that the msg queue contains one more element for reading */ \ sem_getvalue (&(pMsgQueue)->wait, &sem_value); \ if (sem_value <= 0) \ sem_post(&(pMsgQueue)->wait); \ }
可以看到wait信号量被消息循环线程等待。
最后,该函数等待消息循环处理该消息并处罚事先设置的sem_handle信号量。
由于两个线程存在相互等待的情况,所以,需要非常小心的避免引起死锁。
SendSyncMessage函数时不能在一个线程中重入的。否则就会引起死锁。但是不同的线程可以同时调用该函数。
同步消息应用非常广泛,是用到最多的一种消息。但是,在一些方面,却不太适合。如,从底层发送的一些事件,特别是硬件事件;当窗口要再一个很深的消息中删除自己时,如果直接用同步消息,会导致该窗口后续的消息使用了非法指针等等。
所以,这个时候,就应该使用Notify消息。
Notify消息使用SendNotifyMessage发送,它的实现是:
int GUIAPI SendNotifyMessage (HWND hWnd, int iMsg, WPARAM wParam, LPARAM lParam) { PMSGQUEUE pMsgQueue; PQMSG pqmsg; MG_CHECK_RET (MG_IS_WINDOW(hWnd), ERR_INV_HWND); if (!(pMsgQueue = GetMsgQueue(hWnd))) return ERR_INV_HWND; pqmsg = QMSGAlloc(); LOCK_MSGQ (pMsgQueue); /* queue the notification message. */ pqmsg->Msg.hwnd = hWnd; pqmsg->Msg.message = iMsg; pqmsg->Msg.wParam = wParam; pqmsg->Msg.lParam = lParam; pqmsg->next = NULL; if (pMsgQueue->pFirstNotifyMsg == NULL) { pMsgQueue->pFirstNotifyMsg = pMsgQueue->pLastNotifyMsg = pqmsg; } else { pMsgQueue->pLastNotifyMsg->next = pqmsg; pMsgQueue->pLastNotifyMsg = pqmsg; } pMsgQueue->dwState |= QS_NOTIFYMSG; UNLOCK_MSGQ (pMsgQueue); #ifndef _LITE_VERSION if ( !BE_THIS_THREAD(hWnd) ) POST_MSGQ(pMsgQueue); #endif return ERR_OK; }
PostMessage在应用层使用不是很多,不过,在系统内部常常用来发送键盘和鼠标消息,这一点,会在后面详细谈到。因为键盘和鼠标消息如果不能被及时处理,就需要丢掉了。
int GUIAPI PostMessage (HWND hWnd, int iMsg, WPARAM wParam, LPARAM lParam) { PMSGQUEUE pMsgQueue; MSG msg; if (!(pMsgQueue = GetMsgQueue(hWnd))) return ERR_INV_HWND; if (iMsg == MSG_PAINT) { LOCK_MSGQ (pMsgQueue); pMsgQueue->dwState |= QS_PAINT; UNLOCK_MSGQ (pMsgQueue); #ifndef _LITE_VERSION if ( !BE_THIS_THREAD(hWnd) ) POST_MSGQ(pMsgQueue); #endif return ERR_OK; } msg.hwnd = hWnd; msg.message = iMsg; msg.wParam = wParam; msg.lParam = lParam; if (!QueueMessage(pMsgQueue, &msg)) return ERR_QUEUE_FULL; return ERR_OK; }
对MSG_PAINT消息做了特殊处理,piant消息仅仅是增加了标志位
QueueMessage函数的是从固定大小的队列中获取消息,然后处理的:
BOOL QueueMessage (PMSGQUEUE msg_que, PMSG msg) { LOCK_MSGQ(msg_que); /* check whether the last message is MSG_MOUSEMOVE */ if (msg->message == MSG_MOUSEMOVE && msg->hwnd == HWND_DESKTOP && msg_que->readpos != msg_que->writepos) { PMSG last_msg; if (msg_que->writepos == 0) last_msg = msg_que->msg + msg_que->len - 1; else last_msg = msg_que->msg + msg_que->writepos - 1; if (last_msg->message == MSG_MOUSEMOVE && last_msg->wParam == msg->wParam && last_msg->hwnd == msg->hwnd) { last_msg->lParam = msg->lParam; last_msg->time = msg->time; goto ret; } } if ((msg_que->writepos + 1) % msg_que->len == msg_que->readpos) { UNLOCK_MSGQ(msg_que); return FALSE; } /* Write the data and advance write pointer */ msg_que->msg [msg_que->writepos] = *msg; msg_que->writepos++; if (msg_que->writepos >= msg_que->len) msg_que->writepos = 0; ret: msg_que->dwState |= QS_POSTMSG; UNLOCK_MSGQ (msg_que); #ifndef _LITE_VERSION if (!BE_THIS_THREAD (msg->hwnd)) POST_MSGQ (msg_que); #endif return TRUE; }
我们知道,消息循环都是这么写的:
while (GetMessage(&Msg, hMainWnd)) { TranslateMessage(&Msg); DispatchMessage(&Msg); }
static inline BOOL GUIAPI GetMessage (PMSG pMsg, HWND hWnd) { return PeekMessageEx (pMsg, hWnd, 0, 0, TRUE, PM_REMOVE); }
BOOL PeekMessageEx (PMSG pMsg, HWND hWnd, int iMsgFilterMin, int iMsgFilterMax, BOOL bWait, UINT uRemoveMsg) { PMSGQUEUE pMsgQueue; PQMSG phead; if (!pMsg || (hWnd != HWND_DESKTOP && !MG_IS_MAIN_WINDOW(hWnd))) return FALSE; #ifndef _LITE_VERSION if (!(pMsgQueue = GetMsgQueueThisThread ())) return FALSE; #else pMsgQueue = __mg_dsk_msg_queue; #endif memset (pMsg, 0, sizeof(MSG)); checkagain: LOCK_MSGQ (pMsgQueue); if (pMsgQueue->dwState & QS_QUIT) { pMsg->hwnd = hWnd; pMsg->message = MSG_QUIT; pMsg->wParam = 0; pMsg->lParam = 0; SET_PADD (NULL); if (uRemoveMsg == PM_REMOVE) { pMsgQueue->loop_depth --; if (pMsgQueue->loop_depth == 0) pMsgQueue->dwState &= ~QS_QUIT; } UNLOCK_MSGQ (pMsgQueue); return FALSE; } /* Dealing with sync messages before notify messages is better ? */ #ifndef _LITE_VERSION if (pMsgQueue->dwState & QS_SYNCMSG) { if (pMsgQueue->pFirstSyncMsg) { *pMsg = pMsgQueue->pFirstSyncMsg->Msg; SET_PADD (pMsgQueue->pFirstSyncMsg); if (IS_MSG_WANTED(pMsg->message)) { if (uRemoveMsg == PM_REMOVE) { pMsgQueue->pFirstSyncMsg = pMsgQueue->pFirstSyncMsg->pNext; } UNLOCK_MSGQ (pMsgQueue); return TRUE; } } else pMsgQueue->dwState &= ~QS_SYNCMSG; } #endif if (pMsgQueue->dwState & QS_NOTIFYMSG) { if (pMsgQueue->pFirstNotifyMsg) { phead = pMsgQueue->pFirstNotifyMsg; *pMsg = phead->Msg; SET_PADD (NULL); if (IS_MSG_WANTED(pMsg->message)) { if (uRemoveMsg == PM_REMOVE) { pMsgQueue->pFirstNotifyMsg = phead->next; FreeQMSG (phead); } UNLOCK_MSGQ (pMsgQueue); return TRUE; } } else pMsgQueue->dwState &= ~QS_NOTIFYMSG; } if (pMsgQueue->dwState & QS_POSTMSG) { if (pMsgQueue->readpos != pMsgQueue->writepos) { *pMsg = pMsgQueue->msg[pMsgQueue->readpos]; SET_PADD (NULL); if (IS_MSG_WANTED(pMsg->message)) { CheckCapturedMouseMessage (pMsg); if (uRemoveMsg == PM_REMOVE) { pMsgQueue->readpos++; if (pMsgQueue->readpos >= pMsgQueue->len) pMsgQueue->readpos = 0; } UNLOCK_MSGQ (pMsgQueue); return TRUE; } } else pMsgQueue->dwState &= ~QS_POSTMSG; } /* * check invalidate region of the windows */ if (pMsgQueue->dwState & QS_PAINT && IS_MSG_WANTED(MSG_PAINT)) { PMAINWIN pHostingRoot; HWND hNeedPaint; PMAINWIN pWin; #ifndef _LITE_VERSION /* REMIND this */ if (hWnd == HWND_DESKTOP) { pMsg->hwnd = hWnd; pMsg->message = MSG_PAINT; pMsg->wParam = 0; pMsg->lParam = 0; SET_PADD (NULL); if (uRemoveMsg == PM_REMOVE) { pMsgQueue->dwState &= ~QS_PAINT; } UNLOCK_MSGQ (pMsgQueue); return TRUE; } #endif pMsg->message = MSG_PAINT; pMsg->wParam = 0; pMsg->lParam = 0; SET_PADD (NULL); #ifdef _LITE_VERSION pHostingRoot = __mg_dsk_win; #else pHostingRoot = pMsgQueue->pRootMainWin; #endif if ( (hNeedPaint = msgCheckHostedTree (pHostingRoot)) ) { pMsg->hwnd = hNeedPaint; pWin = (PMAINWIN) hNeedPaint; pMsg->lParam = (LPARAM)(&pWin->InvRgn.rgn); UNLOCK_MSGQ (pMsgQueue); return TRUE; } /* no paint message */ pMsgQueue->dwState &= ~QS_PAINT; } /* * handle timer here */ #ifdef _LITE_VERSION if (pMsgQueue->dwState & QS_DESKTIMER) { pMsg->hwnd = HWND_DESKTOP; pMsg->message = MSG_TIMER; pMsg->wParam = 0; pMsg->lParam = 0; if (uRemoveMsg == PM_REMOVE) { pMsgQueue->dwState &= ~QS_DESKTIMER; } return TRUE; } #endif if (pMsgQueue->TimerMask && IS_MSG_WANTED(MSG_TIMER)) { int slot; TIMER* timer; #ifndef _LITE_VERSION if (hWnd == HWND_DESKTOP) { pMsg->hwnd = hWnd; pMsg->message = MSG_TIMER; pMsg->wParam = 0; pMsg->lParam = 0; SET_PADD (NULL); if (uRemoveMsg == PM_REMOVE) { pMsgQueue->TimerMask = 0; } UNLOCK_MSGQ (pMsgQueue); return TRUE; } #endif /* get the first expired timer slot */ slot = pMsgQueue->FirstTimerSlot; do { if (pMsgQueue->TimerMask & (0x01 << slot)) break; slot ++; slot %= DEF_NR_TIMERS; if (slot == pMsgQueue->FirstTimerSlot) { slot = -1; break; } } while (TRUE); pMsgQueue->FirstTimerSlot ++; pMsgQueue->FirstTimerSlot %= DEF_NR_TIMERS; if ((timer = __mg_get_timer (slot))) { unsigned int tick_count = timer->tick_count; timer->tick_count = 0; pMsgQueue->TimerMask &= ~(0x01 << slot); if (timer->proc) { BOOL ret_timer_proc; /* unlock the message queue when calling timer proc */ UNLOCK_MSGQ (pMsgQueue); /* calling the timer callback procedure */ ret_timer_proc = timer->proc (timer->hWnd, timer->id, tick_count); /* lock the message queue again */ LOCK_MSGQ (pMsgQueue); if (!ret_timer_proc) { /* remove the timer */ __mg_remove_timer (timer, slot); } } else { pMsg->message = MSG_TIMER; pMsg->hwnd = timer->hWnd; pMsg->wParam = timer->id; pMsg->lParam = tick_count; SET_PADD (NULL); UNLOCK_MSGQ (pMsgQueue); return TRUE; } } } UNLOCK_MSGQ (pMsgQueue); #ifndef _LITE_VERSION if (bWait) { /* no message, wait again. */ sem_wait (&pMsgQueue->wait); goto checkagain; } #else /* no message, idle */ if (bWait) { pMsgQueue->OnIdle (pMsgQueue); goto checkagain; } #endif /* no message */ return FALSE; }
第二部分,请看函数最后部分,if(bWait)的代码。对于线程版,它就是通过wait信号量,让自己进入休眠。对于进程版和standalone版,它调用OnIdle回调。OnIdle回调在进程版中和线程版转化为对端口的select方法调用,从而导致一个较短时间的休眠。
重点看第一部分,它按照优先级,依次取MSG_QUIT消息,同步消息,notify消息,post消息,和MSG_PAINT消息和MSG_TIMER消息。
普通的消息都比较简单。所以我们重点介绍MSG_PAINT消息和MSG_TIMER消息。
MSG_PAINT消息,其重点是检查了QS_PAINT标志。当有QS_PAINT标志的时候,它实际上通过 msgCheckHostedTree函数,来检查那些窗口是需要重绘的。那些需要重绘的窗口,就会产生MSG_PAINT消息。
msgCheckHostedTree函数,其实现是:
static HWND msgCheckHostedTree (PMAINWIN pHosting) { HWND hNeedPaint; PMAINWIN pHosted; if ( (hNeedPaint = msgCheckInvalidRegion (pHosting)) ) return hNeedPaint; pHosted = pHosting->pFirstHosted; while (pHosted) { if ( (hNeedPaint = msgCheckHostedTree (pHosted)) ) return hNeedPaint; pHosted = pHosted->pNextHosted; } return 0; }msgCheckInvalidRegion函数判断窗口是否存在无效区域,如果存在,则是需要重绘,否则,它会继续查找下个hosted窗口。
这里面涉及很多点:
tatic HWND msgCheckInvalidRegion (PMAINWIN pWin) { PCONTROL pCtrl = (PCONTROL)pWin; HWND hwnd; if (pCtrl->InvRgn.rgn.head) return (HWND)pCtrl; pCtrl = pCtrl->children; while (pCtrl) { if ((hwnd = msgCheckInvalidRegion ((PMAINWIN) pCtrl))) return hwnd; pCtrl = pCtrl->next; } return 0; }
MSG_TIMER消息,在MiniGUI中是一个很特别的消息。它是通过一个Timer服务来支持的。
我们知道,MiniGUI的Timer消息,通过SetTimer和SetTimerEx来启动的。SetTimerEx 函数是主要实现Timer安装的。
在MiniGUI中,Timer的相关信息,通过一个Timer slot来保存的。timer slot实际上一一组数组,数组内的元素可以被重复利用。通过MSGQUEUE中的TimerMark变量,保存那些数组元素被使用了。 TimerMark是用掩码来表示 timerslot的索引的。所以,在MiniGUI最多能够安装32个timer,就是因为TimerMark变量时32位的。
在src/kernel/timer.c文件中,定义了静态变量
static TIMER *timerstr[DEF_NR_TIMERS];其中DEF_NR_TIMERS被定义为32. TIMER结构体定义是:
typedef struct _timer { HWND hWnd; int id; unsigned int speed; unsigned int count; TIMERPROC proc; unsigned int tick_count; PMSGQUEUE msg_queue; } TIMER;一个Timer会和指定的窗口、和窗口依赖的消息队列相关。
当调用SetTimerEx时,实际上,就是使用这样的timer的slot:
BOOL GUIAPI SetTimerEx (HWND hWnd, int id, unsigned int speed, TIMERPROC timer_proc) { int i; PMSGQUEUE pMsgQueue; int slot = -1; #ifndef _LITE_VERSION if (!(pMsgQueue = GetMsgQueueThisThread ())) return FALSE; #else pMsgQueue = __mg_dsk_msg_queue; #endif TIMER_LOCK (); /* Is there an empty timer slot? */ for (i=0; i<DEF_NR_TIMERS; i++) { if (timerstr[i] == NULL) { if (slot < 0) slot = i; } else if (timerstr[i]->hWnd == hWnd && timerstr[i]->id == id) { goto badret; } } if (slot < 0 || slot == DEF_NR_TIMERS) goto badret ; timerstr[slot] = malloc (sizeof (TIMER)); timerstr[slot]->speed = speed; timerstr[slot]->hWnd = hWnd; timerstr[slot]->id = id; timerstr[slot]->count = 0; timerstr[slot]->proc = timer_proc; timerstr[slot]->tick_count = 0; timerstr[slot]->msg_queue = pMsgQueue; #if defined(_LITE_VERSION) && !defined(_STAND_ALONE) if (!mgIsServer) __mg_set_select_timeout (USEC_10MS * speed); #endif TIMER_UNLOCK (); return TRUE; badret: TIMER_UNLOCK (); return FALSE; }
前面的章节中,在讲到MiniGUI初始化的时候,我们提到过,线程版本中,timer会单独使用一个线程,来定时发出消息。该线程入口是TimerEntry,它通过_os_timer_loop来处理线程:
static inline void _os_timer_loop (void) { while (1) { __mg_os_time_delay (10); __mg_timer_action (NULL); } } static void* TimerEntry (void* data) { if (!InitTimer ()) { fprintf (stderr, "TIMER: Init Timer failure, exit!\n"); #ifndef __NOUNIX__ exit (1); #endif return NULL; } sem_post ((sem_t*)data); _os_timer_loop (); return NULL; }
static void __mg_timer_action (void *data) { #if defined(_LITE_VERSION) && !defined(_STAND_ALONE) SHAREDRES_TIMER_COUNTER += 1; #else #if defined(__uClinux__) && defined(_STAND_ALONE) __mg_timer_counter += 10; #else __mg_timer_counter ++; #endif #endif #ifndef _LITE_VERSION /* alert desktop */ AlertDesktopTimerEvent (); #endif }
static inline void AlertDesktopTimerEvent (void) { __mg_dsk_msg_queue->TimerMask = 1; POST_MSGQ(__mg_dsk_msg_queue); }我们知道,Desktop是单独一个线程运行的,其入口是DesktopMain,其核心也是一个消息循环。关键还在于PeekMessageEx中,请注意其中的代码:
if (pMsgQueue->TimerMask && IS_MSG_WANTED(MSG_TIMER)) { int slot; TIMER* timer; #ifndef _LITE_VERSION if (hWnd == HWND_DESKTOP) { pMsg->hwnd = hWnd; pMsg->message = MSG_TIMER; pMsg->wParam = 0; pMsg->lParam = 0; SET_PADD (NULL); if (uRemoveMsg == PM_REMOVE) { pMsgQueue->TimerMask = 0; } UNLOCK_MSGQ (pMsgQueue); return TRUE; } #endif实际上,它发给Desktop的窗口过程的MSG_TIMER处理。Desktop的窗口过程是DesktopWnProc函数,在该函数中主要调用了DispatchTimerMessage函数。该函数的实现是
void DispatchTimerMessage (unsigned int inter) { int i; TIMER_LOCK (); for (i=0; i<DEF_NR_TIMERS; i++) { if (timerstr[i] && timerstr[i]->msg_queue) { timerstr[i]->count += inter; if (timerstr[i]->count >= timerstr[i]->speed) { if (timerstr[i]->tick_count == 0) #if defined(_LITE_VERSION) && !defined(_STAND_ALONE) timerstr[i]->tick_count = SHAREDRES_TIMER_COUNTER; #else timerstr[i]->tick_count = __mg_timer_counter; #endif /* setting timer flag is simple, we do not need to lock msgq, or else we may encounter dead lock here */ SetMsgQueueTimerFlag (timerstr[i]->msg_queue, i); timerstr[i]->count -= timerstr[i]->speed; } } } TIMER_UNLOCK (); }
很明显,该函数最重要的地方,是调用SetMsgQueueTimerFlag,把MSGQUEUE的TimerMark变量打上掩码标记,并唤醒消息队列。
消息队列的PeekMessageEx函数,就会从特定的timerstr中取得对应的信息,并形成MSG_TIMER消息。
下面的一个序列图简要说明了MSG_TIMER的产生过过程: