MiniGUI源码分析--Helloworld(3):消息概览

这本篇中,将重点介绍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。)

[cpp]  view plain copy
  1. struct _MSGQUEUE  
  2. {  
  3.     DWORD dwState;              // message queue states  
  4.   
  5. #ifndef _LITE_VERSION  
  6.     pthread_mutex_t lock;       // lock  
  7.     sem_t wait;                 // the semaphore for wait message  
  8.     sem_t sync_msg;             // the semaphore for sync message  
  9. #endif  
  10.   
  11.     PQMSG  pFirstNotifyMsg;     // head of the notify message queue  
  12.     PQMSG  pLastNotifyMsg;      // tail of the notify message queue  
  13.   
  14. #ifndef _LITE_VERSION  
  15.     PSYNCMSG pFirstSyncMsg;     // head of the sync message queue  
  16.     PSYNCMSG pLastSyncMsg;      // tail of the sync message queue  
  17. #else  
  18.     IDLEHANDLER OnIdle;         // Idle handler  
  19. #endif  
  20.   
  21. #ifndef _LITE_VERSION  
  22.     PMAINWIN pRootMainWin;      // The root main window of this message queue.  
  23. #endif  
  24.   
  25.     MSG* msg;                   /* post message buffer */  
  26.     int len;                    /* buffer len */  
  27.     int readpos, writepos;      /* positions for reading and writing */  
  28.   
  29.     int FirstTimerSlot;         /* the first timer slot to be checked */  
  30.     DWORD TimerMask;            /* timer slots mask */  
  31.   
  32.     int loop_depth;             /* message loop depth, for dialog boxes. */  
  33. };  


dwState是一个标志位,它的定义有:

[cpp]  view plain copy
  1. #define QS_NOTIFYMSG        0x10000000  
  2. #ifndef _LITE_VERSION  
  3.   #define QS_SYNCMSG        0x20000000  
  4. #else  
  5.   #define QS_DESKTIMER      0x20000000  
  6. #endif  
  7. #define QS_POSTMSG          0x40000000  
  8. #define QS_QUIT             0x80000000  
  9. #define QS_INPUT            0x01000000  
  10. #define QS_PAINT            0x02000000  
  11. #define QS_TIMER            0x0000FFFF  
  12. #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代码

[cpp]  view plain copy
  1. int GUIAPI SendMessage (HWND hWnd, int iMsg, WPARAM wParam, LPARAM lParam)  
  2. {  
  3.     WNDPROC WndProc;  
  4.   
  5.     MG_CHECK_RET (MG_IS_WINDOW(hWnd), -1);  
  6.   
  7. #ifndef _LITE_VERSION  
  8.     if (!BE_THIS_THREAD(hWnd))  
  9.         return SendSyncMessage (hWnd, iMsg, wParam, lParam);  
  10. #endif /* !_LITE_VERSION */  
  11.       
  12.     if ( !(WndProc = GetWndProc(hWnd)) )  
  13.         return ERR_INV_HWND;  
  14.   
  15.     return (*WndProc)(hWnd, iMsg, wParam, lParam);  
BE_IS_THREAD宏通过hWnd的住窗口内保持的线程句柄来判断的。


当不是一个线程时,就调用SendSyncMessage。这个函数

[cpp]  view plain copy
  1. int SendSyncMessage (HWND hWnd, int msg, WPARAM wParam, LPARAM lParam)  
  2. {  
  3.     PMSGQUEUE pMsgQueue, thinfo = NULL;  
  4.     SYNCMSG SyncMsg;  
  5.     sem_t sync_msg;  
  6.   
  7.     if (!(pMsgQueue = GetMsgQueue(hWnd)))  
  8.         return ERR_INV_HWND;  
  9.   
  10.     if ((thinfo = GetMsgQueueThisThread ())) {  
  11.         /* avoid to create a new semaphore object */  
  12.         SyncMsg.sem_handle = &thinfo->sync_msg;  
  13.     }  
  14.     else {  
  15.         /* this is not a GUI thread */  
  16.         sem_init (&sync_msg, 0, 0);  
  17.         SyncMsg.sem_handle = &sync_msg;  
  18.     }  
  19.   
  20.     /* queue the sync message. */  
  21.     SyncMsg.Msg.hwnd = hWnd;  
  22.     SyncMsg.Msg.message = msg;  
  23.     SyncMsg.Msg.wParam = wParam;  
  24.     SyncMsg.Msg.lParam = lParam;  
  25.     SyncMsg.retval = ERR_MSG_CANCELED;  
  26.     SyncMsg.pNext = NULL;  
  27.   
  28.     LOCK_MSGQ (pMsgQueue);  
  29.   
  30.     if (pMsgQueue->pFirstSyncMsg == NULL) {  
  31.         pMsgQueue->pFirstSyncMsg = pMsgQueue->pLastSyncMsg = &SyncMsg;  
  32.     }  
  33.     else {  
  34.         pMsgQueue->pLastSyncMsg->pNext = &SyncMsg;  
  35.         pMsgQueue->pLastSyncMsg = &SyncMsg;  
  36.     }  
  37.   
  38.     pMsgQueue->dwState |= QS_SYNCMSG;  
  39.   
  40.     UNLOCK_MSGQ (pMsgQueue);  
  41.     POST_MSGQ (pMsgQueue);  
  42.   
  43.     /* suspend until the message has been handled. */  
  44.     if (sem_wait (SyncMsg.sem_handle) < 0) {  
  45.         fprintf (stderr,   
  46.             "SendSyncMessage: thread is interrupted abnormally!\n");  
  47.     }  
  48.   
  49.     if (thinfo == NULL)  
  50.         sem_destroy (&sync_msg);  
  51.   
  52.     return SyncMsg.retval;  
  53. }  


注意中间几行处理pMsgQueue->pFirstSyncMsg的语句。由于是跨线程同步处理,所以,该函数直接使用栈上变量,这是一个简单的技巧。


当把消息添加到队里中后,通过POST_MSGQ宏,唤醒消息循环,该宏的定义是:

[cpp]  view plain copy
  1. #define POST_MSGQ(pMsgQueue) \  
  2. { \  
  3.   int sem_value; \  
  4.   /* Signal that the msg queue contains one more element for reading */ \  
  5.   sem_getvalue (&(pMsgQueue)->wait, &sem_value); \  
  6.   if (sem_value <= 0) \  
  7.       sem_post(&(pMsgQueue)->wait); \  
  8. }  

可以看到wait信号量被消息循环线程等待。

最后,该函数等待消息循环处理该消息并处罚事先设置的sem_handle信号量。

由于两个线程存在相互等待的情况,所以,需要非常小心的避免引起死锁。

SendSyncMessage函数时不能在一个线程中重入的。否则就会引起死锁。但是不同的线程可以同时调用该函数。


同步消息应用非常广泛,是用到最多的一种消息。但是,在一些方面,却不太适合。如,从底层发送的一些事件,特别是硬件事件;当窗口要再一个很深的消息中删除自己时,如果直接用同步消息,会导致该窗口后续的消息使用了非法指针等等。

所以,这个时候,就应该使用Notify消息。

Notify消息使用SendNotifyMessage发送,它的实现是:

[cpp]  view plain copy
  1. int GUIAPI SendNotifyMessage (HWND hWnd, int iMsg, WPARAM wParam, LPARAM lParam)  
  2. {  
  3.     PMSGQUEUE pMsgQueue;  
  4.     PQMSG pqmsg;  
  5.   
  6.     MG_CHECK_RET (MG_IS_WINDOW(hWnd), ERR_INV_HWND);  
  7.   
  8.     if (!(pMsgQueue = GetMsgQueue(hWnd)))  
  9.         return ERR_INV_HWND;  
  10.     
  11.     pqmsg = QMSGAlloc();  
  12.   
  13.     LOCK_MSGQ (pMsgQueue);  
  14.   
  15.     /* queue the notification message. */  
  16.     pqmsg->Msg.hwnd = hWnd;  
  17.     pqmsg->Msg.message = iMsg;  
  18.     pqmsg->Msg.wParam = wParam;  
  19.     pqmsg->Msg.lParam = lParam;  
  20.     pqmsg->next = NULL;  
  21.   
  22.     if (pMsgQueue->pFirstNotifyMsg == NULL) {  
  23.         pMsgQueue->pFirstNotifyMsg = pMsgQueue->pLastNotifyMsg = pqmsg;  
  24.     }  
  25.     else {  
  26.         pMsgQueue->pLastNotifyMsg->next = pqmsg;  
  27.         pMsgQueue->pLastNotifyMsg = pqmsg;  
  28.     }  
  29.   
  30.     pMsgQueue->dwState |= QS_NOTIFYMSG;  
  31.   
  32.     UNLOCK_MSGQ (pMsgQueue);  
  33. #ifndef _LITE_VERSION  
  34.     if ( !BE_THIS_THREAD(hWnd) )  
  35.         POST_MSGQ(pMsgQueue);  
  36. #endif  
  37.   
  38.     return ERR_OK;  
  39. }  

不同于SendSyncMessage,该消息时异步的。把消息放入队列后,就立即退出。所以,它是通过QMSGAlloc分配的消息。这个时候, 如果在消息的wParam和lParam放入指针,就需要非常小心的在合适时机释放内存,以避免内存泄露的产生


PostMessage在应用层使用不是很多,不过,在系统内部常常用来发送键盘和鼠标消息,这一点,会在后面详细谈到。因为键盘和鼠标消息如果不能被及时处理,就需要丢掉了。

[cpp]  view plain copy
  1. int GUIAPI PostMessage (HWND hWnd, int iMsg, WPARAM wParam, LPARAM lParam)  
  2. {  
  3.     PMSGQUEUE pMsgQueue;  
  4.     MSG msg;  
  5.   
  6.     if (!(pMsgQueue = GetMsgQueue(hWnd)))  
  7.         return ERR_INV_HWND;  
  8.   
  9.     if (iMsg == MSG_PAINT) {  
  10.         LOCK_MSGQ (pMsgQueue);  
  11.         pMsgQueue->dwState |= QS_PAINT;  
  12.         UNLOCK_MSGQ (pMsgQueue);  
  13. #ifndef _LITE_VERSION  
  14.         if ( !BE_THIS_THREAD(hWnd) )  
  15.             POST_MSGQ(pMsgQueue);  
  16. #endif  
  17.         return ERR_OK;  
  18.     }  
  19.   
  20.     msg.hwnd = hWnd;  
  21.     msg.message = iMsg;  
  22.     msg.wParam = wParam;  
  23.     msg.lParam = lParam;  
  24.   
  25.     if (!QueueMessage(pMsgQueue, &msg))  
  26.         return ERR_QUEUE_FULL;  
  27.   
  28.     return ERR_OK;  
  29. }  

它与SendNotifyMessage的区别主要在于:

对MSG_PAINT消息做了特殊处理,piant消息仅仅是增加了标志位

QueueMessage函数的是从固定大小的队列中获取消息,然后处理的:

[cpp]  view plain copy
  1. BOOL QueueMessage (PMSGQUEUE msg_que, PMSG msg)  
  2. {  
  3.     LOCK_MSGQ(msg_que);  
  4.   
  5.     /* check whether the last message is MSG_MOUSEMOVE */  
  6.     if (msg->message == MSG_MOUSEMOVE && msg->hwnd == HWND_DESKTOP  
  7.                     && msg_que->readpos != msg_que->writepos) {  
  8.         PMSG last_msg;  
  9.   
  10.         if (msg_que->writepos == 0)  
  11.             last_msg = msg_que->msg + msg_que->len - 1;  
  12.         else  
  13.             last_msg = msg_que->msg + msg_que->writepos - 1;  
  14.   
  15.         if (last_msg->message == MSG_MOUSEMOVE  
  16.                         && last_msg->wParam == msg->wParam  
  17.                         && last_msg->hwnd == msg->hwnd) {  
  18.             last_msg->lParam = msg->lParam;  
  19.             last_msg->time = msg->time;  
  20.             goto ret;  
  21.         }  
  22.     }  
  23.   
  24.     if ((msg_que->writepos + 1) % msg_que->len == msg_que->readpos) {  
  25.         UNLOCK_MSGQ(msg_que);  
  26.         return FALSE;  
  27.     }  
  28.   
  29.     /* Write the data and advance write pointer */  
  30.     msg_que->msg [msg_que->writepos] = *msg;  
  31.   
  32.     msg_que->writepos++;  
  33.     if (msg_que->writepos >= msg_que->len) msg_que->writepos = 0;  
  34.   
  35. ret:  
  36.     msg_que->dwState |= QS_POSTMSG;  
  37.   
  38.     UNLOCK_MSGQ (msg_que);  
  39.   
  40. #ifndef _LITE_VERSION  
  41.     if (!BE_THIS_THREAD (msg->hwnd))  
  42.         POST_MSGQ (msg_que);  
  43. #endif  
  44.   
  45.     return TRUE;  
  46. }  

readpos和writepos一个是队列头一个是队列尾,MSGQUEUE中的msg是一个数组,在InitMsgQueue分配了指定长度的数组。



我们知道,消息循环都是这么写的:

[cpp]  view plain copy
  1. while (GetMessage(&Msg, hMainWnd)) {    
  2.        TranslateMessage(&Msg);    
  3.        DispatchMessage(&Msg);    
  4.     

GetMessage函数,它的实现是

[cpp]  view plain copy
  1. static inline BOOL GUIAPI GetMessage (PMSG pMsg, HWND hWnd)  
  2. {  
  3.     return PeekMessageEx (pMsg, hWnd, 0, 0, TRUE, PM_REMOVE);  
  4. }  

PeekMessageEx函数时从消息队列中检出消息。它的实现

[cpp]  view plain copy
  1. BOOL PeekMessageEx (PMSG pMsg, HWND hWnd, int iMsgFilterMin, int iMsgFilterMax,   
  2.                           BOOL bWait, UINT uRemoveMsg)  
  3. {  
  4.     PMSGQUEUE pMsgQueue;  
  5.     PQMSG phead;  
  6.   
  7.     if (!pMsg || (hWnd != HWND_DESKTOP && !MG_IS_MAIN_WINDOW(hWnd)))  
  8.         return FALSE;  
  9.   
  10. #ifndef _LITE_VERSION  
  11.     if (!(pMsgQueue = GetMsgQueueThisThread ()))  
  12.             return FALSE;  
  13. #else  
  14.     pMsgQueue = __mg_dsk_msg_queue;  
  15. #endif  
  16.   
  17.     memset (pMsg, 0, sizeof(MSG));  
  18.   
  19. checkagain:  
  20.   
  21.     LOCK_MSGQ (pMsgQueue);  
  22.   
  23.     if (pMsgQueue->dwState & QS_QUIT) {  
  24.         pMsg->hwnd = hWnd;  
  25.         pMsg->message = MSG_QUIT;  
  26.         pMsg->wParam = 0;  
  27.         pMsg->lParam = 0;  
  28.         SET_PADD (NULL);  
  29.   
  30.         if (uRemoveMsg == PM_REMOVE) {  
  31.             pMsgQueue->loop_depth --;  
  32.             if (pMsgQueue->loop_depth == 0)  
  33.                 pMsgQueue->dwState &= ~QS_QUIT;  
  34.         }  
  35.    
  36.         UNLOCK_MSGQ (pMsgQueue);  
  37.         return FALSE;  
  38.     }  
  39.   
  40.     /* Dealing with sync messages before notify messages is better ? */  
  41. #ifndef _LITE_VERSION  
  42.     if (pMsgQueue->dwState & QS_SYNCMSG) {  
  43.         if (pMsgQueue->pFirstSyncMsg) {  
  44.             *pMsg = pMsgQueue->pFirstSyncMsg->Msg;  
  45.             SET_PADD (pMsgQueue->pFirstSyncMsg);  
  46.             if (IS_MSG_WANTED(pMsg->message)) {  
  47.               if (uRemoveMsg == PM_REMOVE) {  
  48.                   pMsgQueue->pFirstSyncMsg = pMsgQueue->pFirstSyncMsg->pNext;  
  49.               }  
  50.   
  51.               UNLOCK_MSGQ (pMsgQueue);  
  52.               return TRUE;  
  53.             }  
  54.         }  
  55.         else  
  56.             pMsgQueue->dwState &= ~QS_SYNCMSG;  
  57.     }  
  58. #endif  
  59.   
  60.     if (pMsgQueue->dwState & QS_NOTIFYMSG) {  
  61.         if (pMsgQueue->pFirstNotifyMsg) {  
  62.             phead = pMsgQueue->pFirstNotifyMsg;  
  63.             *pMsg = phead->Msg;  
  64.             SET_PADD (NULL);  
  65.   
  66.             if (IS_MSG_WANTED(pMsg->message)) {  
  67.               if (uRemoveMsg == PM_REMOVE) {  
  68.                   pMsgQueue->pFirstNotifyMsg = phead->next;  
  69.                   FreeQMSG (phead);  
  70.               }  
  71.   
  72.               UNLOCK_MSGQ (pMsgQueue);  
  73.               return TRUE;  
  74.             }  
  75.         }  
  76.         else  
  77.             pMsgQueue->dwState &= ~QS_NOTIFYMSG;  
  78.     }  
  79.   
  80.     if (pMsgQueue->dwState & QS_POSTMSG) {  
  81.         if (pMsgQueue->readpos != pMsgQueue->writepos) {  
  82.             *pMsg = pMsgQueue->msg[pMsgQueue->readpos];  
  83.             SET_PADD (NULL);  
  84.             if (IS_MSG_WANTED(pMsg->message)) {  
  85.                 CheckCapturedMouseMessage (pMsg);  
  86.                 if (uRemoveMsg == PM_REMOVE) {  
  87.                     pMsgQueue->readpos++;  
  88.                     if (pMsgQueue->readpos >= pMsgQueue->len)  
  89.                         pMsgQueue->readpos = 0;  
  90.                 }  
  91.   
  92.                 UNLOCK_MSGQ (pMsgQueue);  
  93.                 return TRUE;  
  94.             }  
  95.         }  
  96.         else  
  97.             pMsgQueue->dwState &= ~QS_POSTMSG;  
  98.     }  
  99.   
  100.     /* 
  101.      * check invalidate region of the windows 
  102.      */  
  103.   
  104.     if (pMsgQueue->dwState & QS_PAINT && IS_MSG_WANTED(MSG_PAINT)) {  
  105.         PMAINWIN pHostingRoot;  
  106.         HWND hNeedPaint;  
  107.         PMAINWIN pWin;  
  108.    
  109. #ifndef _LITE_VERSION  
  110.         /* REMIND this */  
  111.         if (hWnd == HWND_DESKTOP) {  
  112.             pMsg->hwnd = hWnd;  
  113.             pMsg->message = MSG_PAINT;  
  114.             pMsg->wParam = 0;  
  115.             pMsg->lParam = 0;  
  116.             SET_PADD (NULL);  
  117.   
  118.             if (uRemoveMsg == PM_REMOVE) {  
  119.                 pMsgQueue->dwState &= ~QS_PAINT;  
  120.             }  
  121.             UNLOCK_MSGQ (pMsgQueue);  
  122.             return TRUE;  
  123.         }  
  124. #endif  
  125.   
  126.         pMsg->message = MSG_PAINT;  
  127.         pMsg->wParam = 0;  
  128.         pMsg->lParam = 0;  
  129.         SET_PADD (NULL);  
  130.   
  131. #ifdef _LITE_VERSION  
  132.         pHostingRoot = __mg_dsk_win;  
  133. #else  
  134.         pHostingRoot = pMsgQueue->pRootMainWin;  
  135. #endif  
  136.   
  137.         if ( (hNeedPaint = msgCheckHostedTree (pHostingRoot)) ) {  
  138.             pMsg->hwnd = hNeedPaint;  
  139.             pWin = (PMAINWIN) hNeedPaint;  
  140.             pMsg->lParam = (LPARAM)(&pWin->InvRgn.rgn);  
  141.             UNLOCK_MSGQ (pMsgQueue);  
  142.             return TRUE;  
  143.         }  
  144.    
  145.         /* no paint message */  
  146.         pMsgQueue->dwState &= ~QS_PAINT;  
  147.     }  
  148.   
  149.     /* 
  150.      * handle timer here 
  151.      */  
  152. #ifdef _LITE_VERSION  
  153.     if (pMsgQueue->dwState & QS_DESKTIMER) {  
  154.         pMsg->hwnd = HWND_DESKTOP;  
  155.         pMsg->message = MSG_TIMER;  
  156.         pMsg->wParam = 0;  
  157.         pMsg->lParam = 0;  
  158.   
  159.         if (uRemoveMsg == PM_REMOVE) {  
  160.             pMsgQueue->dwState &= ~QS_DESKTIMER;  
  161.         }  
  162.         return TRUE;  
  163.     }  
  164. #endif  
  165.   
  166.     if (pMsgQueue->TimerMask && IS_MSG_WANTED(MSG_TIMER)) {  
  167.         int slot;  
  168.         TIMER* timer;  
  169.   
  170. #ifndef _LITE_VERSION  
  171.         if (hWnd == HWND_DESKTOP) {  
  172.             pMsg->hwnd = hWnd;  
  173.             pMsg->message = MSG_TIMER;  
  174.             pMsg->wParam = 0;  
  175.             pMsg->lParam = 0;  
  176.             SET_PADD (NULL);  
  177.   
  178.             if (uRemoveMsg == PM_REMOVE) {  
  179.                 pMsgQueue->TimerMask = 0;  
  180.             }  
  181.             UNLOCK_MSGQ (pMsgQueue);  
  182.             return TRUE;  
  183.         }  
  184. #endif  
  185.   
  186.         /* get the first expired timer slot */  
  187.         slot = pMsgQueue->FirstTimerSlot;  
  188.         do {  
  189.             if (pMsgQueue->TimerMask & (0x01 << slot))  
  190.                 break;  
  191.   
  192.             slot ++;  
  193.             slot %= DEF_NR_TIMERS;  
  194.             if (slot == pMsgQueue->FirstTimerSlot) {  
  195.                 slot = -1;  
  196.                 break;  
  197.             }  
  198.         } while (TRUE);  
  199.   
  200.         pMsgQueue->FirstTimerSlot ++;  
  201.         pMsgQueue->FirstTimerSlot %= DEF_NR_TIMERS;  
  202.   
  203.         if ((timer = __mg_get_timer (slot))) {  
  204.   
  205.             unsigned int tick_count = timer->tick_count;  
  206.   
  207.             timer->tick_count = 0;  
  208.             pMsgQueue->TimerMask &= ~(0x01 << slot);  
  209.   
  210.             if (timer->proc) {  
  211.                 BOOL ret_timer_proc;  
  212.   
  213.                 /* unlock the message queue when calling timer proc */  
  214.                 UNLOCK_MSGQ (pMsgQueue);  
  215.   
  216.                 /* calling the timer callback procedure */  
  217.                 ret_timer_proc = timer->proc (timer->hWnd,   
  218.                         timer->id, tick_count);  
  219.   
  220.                 /* lock the message queue again */  
  221.                 LOCK_MSGQ (pMsgQueue);  
  222.   
  223.                 if (!ret_timer_proc) {  
  224.                     /* remove the timer */  
  225.                     __mg_remove_timer (timer, slot);  
  226.                 }  
  227.             }  
  228.             else {  
  229.                 pMsg->message = MSG_TIMER;  
  230.                 pMsg->hwnd = timer->hWnd;  
  231.                 pMsg->wParam = timer->id;  
  232.                 pMsg->lParam = tick_count;  
  233.                 SET_PADD (NULL);  
  234.   
  235.                 UNLOCK_MSGQ (pMsgQueue);  
  236.                 return TRUE;  
  237.             }  
  238.         }  
  239.     }  
  240.   
  241.     UNLOCK_MSGQ (pMsgQueue);  
  242.   
  243. #ifndef _LITE_VERSION  
  244.     if (bWait) {  
  245.         /* no message, wait again. */  
  246.         sem_wait (&pMsgQueue->wait);  
  247.         goto checkagain;  
  248.     }  
  249. #else  
  250.     /* no message, idle */  
  251.     if (bWait) {  
  252.          pMsgQueue->OnIdle (pMsgQueue);  
  253.          goto checkagain;  
  254.     }  
  255. #endif  
  256.   
  257.     /* no message */  
  258.     return FALSE;  
  259. }  

内容很长,但是可以分成两部分看:第一步部分,是获取消息的部分;第二部分,是等待消息循环的部分。

第二部分,请看函数最后部分,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函数,其实现是:

[cpp]  view plain copy
  1. static HWND msgCheckHostedTree (PMAINWIN pHosting)  
  2. {  
  3.     HWND hNeedPaint;  
  4.     PMAINWIN pHosted;  
  5.   
  6.     if ( (hNeedPaint = msgCheckInvalidRegion (pHosting)) )  
  7.         return hNeedPaint;  
  8.   
  9.     pHosted = pHosting->pFirstHosted;  
  10.     while (pHosted) {  
  11.         if ( (hNeedPaint = msgCheckHostedTree (pHosted)) )  
  12.             return hNeedPaint;  
  13.   
  14.         pHosted = pHosted->pNextHosted;  
  15.     }  
  16.   
  17.     return 0;  
  18. }  
msgCheckInvalidRegion函数判断窗口是否存在无效区域,如果存在,则是需要重绘,否则,它会继续查找下个hosted窗口。


这里面涉及很多点:

  1. MiniGUI窗口的管理方式,是主窗口和普通窗口分离的。主窗口采用hosting的链表树方式管理,可以遍历到所有的主窗口。而每个主窗口则是一个树的根节点,通过parent-child关系管理所有的子窗口。所以,msgCheckHostedTree是遍历主窗口用的,而msgCheckInvalidRegion是遍历子窗口用的。注意这两个函数的返回值,都是需要重绘的窗口句柄。
  2. 当找到一个需要重绘的窗口句柄的时候,它就返回该窗口句柄。返回后,消息循环接下来回调用DispatchMessage,该函数会调用到窗口过程。在窗口过程中,处理MSG_PAINT消息时,我们必须用BeginPaint和EndPaint来获取绘制的DC。这一点非常重要,因为在BeginPaint中,会清除无效区域。当无效区域被清除后,下次在此调用msgCheckHostedTree和msgCheckInvalidRegion的时候,该窗口的子窗口或者兄弟窗口就会被检测。一直等到所有窗口被检测完毕后,整个绘制过程才算完成。在此过程中,MiniGUI会多次遍历所有的窗口
  3. msgCheckHostedTree和msgCheckInvalidRegion相结合使用,是通过递归的方法进行的一个先根遍历法,这个方法,总是能够保证父窗口先于子窗口被绘制,从而保证了窗口之间以正确的次序被绘制。
msgCheckInvalidRegion的函数定义如下:

[cpp]  view plain copy
  1. tatic HWND msgCheckInvalidRegion (PMAINWIN pWin)  
  2. {  
  3.     PCONTROL pCtrl = (PCONTROL)pWin;  
  4.     HWND hwnd;  
  5.   
  6.     if (pCtrl->InvRgn.rgn.head)  
  7.         return (HWND)pCtrl;  
  8.   
  9.     pCtrl = pCtrl->children;  
  10.     while (pCtrl) {  
  11.   
  12.         if ((hwnd = msgCheckInvalidRegion ((PMAINWIN) pCtrl)))  
  13.             return hwnd;  
  14.   
  15.         pCtrl = pCtrl->next;  
  16.     }  
  17.   
  18.     return 0;  
  19. }  

他是通过判断窗口结构体中的 InvRgn的区域是否为空来实现的。它的实现,是一个典型的先根遍历法,可以保证窗口的绘制次序。



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文件中,定义了静态变量

[cpp]  view plain copy
  1. static TIMER *timerstr[DEF_NR_TIMERS];  
其中DEF_NR_TIMERS被定义为32. TIMER结构体定义是:

[cpp]  view plain copy
  1. typedef struct _timer {  
  2.     HWND    hWnd;  
  3.     int     id;  
  4.     unsigned int speed;  
  5.     unsigned int count;  
  6.   
  7.     TIMERPROC proc;  
  8.     unsigned int tick_count;  
  9.     PMSGQUEUE msg_queue;  
  10. } TIMER;  
一个Timer会和指定的窗口、和窗口依赖的消息队列相关。


当调用SetTimerEx时,实际上,就是使用这样的timer的slot:

[cpp]  view plain copy
  1. BOOL GUIAPI SetTimerEx (HWND hWnd, int id, unsigned int speed,   
  2.                 TIMERPROC timer_proc)  
  3. {  
  4.     int i;  
  5.     PMSGQUEUE pMsgQueue;  
  6.     int slot = -1;  
  7.   
  8. #ifndef _LITE_VERSION  
  9.     if (!(pMsgQueue = GetMsgQueueThisThread ()))  
  10.         return FALSE;  
  11. #else  
  12.     pMsgQueue = __mg_dsk_msg_queue;  
  13. #endif  
  14.   
  15.     TIMER_LOCK ();  
  16.   
  17.     /* Is there an empty timer slot? */  
  18.     for (i=0; i<DEF_NR_TIMERS; i++) {  
  19.         if (timerstr[i] == NULL) {  
  20.             if (slot < 0)  
  21.                 slot = i;  
  22.         }  
  23.         else if (timerstr[i]->hWnd == hWnd && timerstr[i]->id == id) {  
  24.             goto badret;  
  25.         }  
  26.     }  
  27.   
  28.     if (slot < 0 || slot == DEF_NR_TIMERS)  
  29.         goto badret ;  
  30.   
  31.     timerstr[slot] = malloc (sizeof (TIMER));  
  32.   
  33.     timerstr[slot]->speed = speed;  
  34.     timerstr[slot]->hWnd = hWnd;  
  35.     timerstr[slot]->id = id;  
  36.     timerstr[slot]->count = 0;  
  37.     timerstr[slot]->proc = timer_proc;  
  38.     timerstr[slot]->tick_count = 0;  
  39.     timerstr[slot]->msg_queue = pMsgQueue;  
  40.   
  41. #if defined(_LITE_VERSION) && !defined(_STAND_ALONE)  
  42.     if (!mgIsServer)  
  43.         __mg_set_select_timeout (USEC_10MS * speed);  
  44. #endif  
  45.   
  46.     TIMER_UNLOCK ();  
  47.     return TRUE;  
  48.       
  49. badret:  
  50.     TIMER_UNLOCK ();  
  51.     return FALSE;  
  52. }  



前面的章节中,在讲到MiniGUI初始化的时候,我们提到过,线程版本中,timer会单独使用一个线程,来定时发出消息。该线程入口是TimerEntry,它通过_os_timer_loop来处理线程:

[cpp]  view plain copy
  1. static inline void _os_timer_loop (void)  
  2. {  
  3.     while (1) {  
  4.         __mg_os_time_delay (10);  
  5.         __mg_timer_action (NULL);  
  6.     }  
  7. }  
  8.   
  9. static void* TimerEntry (void* data)  
  10. {  
  11.     if (!InitTimer ()) {  
  12.         fprintf (stderr, "TIMER: Init Timer failure, exit!\n");  
  13. #ifndef __NOUNIX__  
  14.         exit (1);  
  15. #endif  
  16.         return NULL;  
  17.     }  
  18.     sem_post ((sem_t*)data);  
  19.   
  20.     _os_timer_loop ();  
  21.   
  22.     return NULL;  
  23. }  


_os_timer_loop是每10毫秒发出一次消息,通过__mg_timer_action函数:

[cpp]  view plain copy
  1. static void __mg_timer_action (void *data)  
  2. {  
  3. #if defined(_LITE_VERSION) && !defined(_STAND_ALONE)  
  4.     SHAREDRES_TIMER_COUNTER += 1;  
  5. #else  
  6.   
  7. #if defined(__uClinux__) && defined(_STAND_ALONE)  
  8.     __mg_timer_counter += 10;  
  9. #else  
  10.     __mg_timer_counter ++;  
  11. #endif  
  12.   
  13. #endif  
  14.   
  15. #ifndef _LITE_VERSION  
  16.     /* alert desktop */  
  17.     AlertDesktopTimerEvent ();  
  18. #endif  
  19. }  

关键函数是AlertDesktopTimerEvent:

[cpp]  view plain copy
  1. static inline void   
  2. AlertDesktopTimerEvent (void)  
  3. {  
  4.     __mg_dsk_msg_queue->TimerMask = 1;  
  5.     POST_MSGQ(__mg_dsk_msg_queue);  
  6. }  
我们知道,Desktop是单独一个线程运行的,其入口是DesktopMain,其核心也是一个消息循环。关键还在于PeekMessageEx中,请注意其中的代码:

[cpp]  view plain copy
  1.     if (pMsgQueue->TimerMask && IS_MSG_WANTED(MSG_TIMER)) {  
  2.         int slot;  
  3.         TIMER* timer;  
  4.   
  5. #ifndef _LITE_VERSION  
  6.         if (hWnd == HWND_DESKTOP) {  
  7.             pMsg->hwnd = hWnd;  
  8.             pMsg->message = MSG_TIMER;  
  9.             pMsg->wParam = 0;  
  10.             pMsg->lParam = 0;  
  11.             SET_PADD (NULL);  
  12.   
  13.             if (uRemoveMsg == PM_REMOVE) {  
  14.                 pMsgQueue->TimerMask = 0;  
  15.             }  
  16.             UNLOCK_MSGQ (pMsgQueue);  
  17.             return TRUE;  
  18.         }  
  19. #endif  
实际上,它发给Desktop的窗口过程的MSG_TIMER处理。Desktop的窗口过程是DesktopWnProc函数,在该函数中主要调用了DispatchTimerMessage函数。该函数的实现是

[cpp]  view plain copy
  1. void DispatchTimerMessage (unsigned int inter)  
  2. {  
  3.     int i;  
  4.   
  5.     TIMER_LOCK ();  
  6.   
  7.     for (i=0; i<DEF_NR_TIMERS; i++) {  
  8.         if (timerstr[i] && timerstr[i]->msg_queue) {  
  9.             timerstr[i]->count += inter;  
  10.             if (timerstr[i]->count >= timerstr[i]->speed) {  
  11.                 if (timerstr[i]->tick_count == 0)  
  12. #if defined(_LITE_VERSION) && !defined(_STAND_ALONE)  
  13.                     timerstr[i]->tick_count = SHAREDRES_TIMER_COUNTER;  
  14. #else  
  15.                     timerstr[i]->tick_count = __mg_timer_counter;  
  16. #endif  
  17.                 /* setting timer flag is simple, we do not need to lock msgq, 
  18.                    or else we may encounter dead lock here */   
  19.                 SetMsgQueueTimerFlag (timerstr[i]->msg_queue, i);  
  20.                   
  21.                 timerstr[i]->count -= timerstr[i]->speed;  
  22.             }  
  23.         }  
  24.     }  
  25.   
  26.     TIMER_UNLOCK ();  
  27. }  

很明显,该函数最重要的地方,是调用SetMsgQueueTimerFlag,把MSGQUEUE的TimerMark变量打上掩码标记,并唤醒消息队列。


消息队列的PeekMessageEx函数,就会从特定的timerstr中取得对应的信息,并形成MSG_TIMER消息。


下面的一个序列图简要说明了MSG_TIMER的产生过过程:


MiniGUI源码分析--Helloworld(3):消息概览_第1张图片

你可能感兴趣的:(helloworld,源码分析,MINIGUI,消息概览)