miniGUI消息隊(duì)列分析

miniGUI消息隊(duì)列的定義

miniGUI(版本:libminigui-gpl-3.0.12)的消息隊(duì)列支持通知消息、同步消息和普通消息三種消息類型,_MSGQUEUE消息隊(duì)列結(jié)構(gòu)容納了這三種消息隊(duì)列,也就是說(shuō)真正的消息隊(duì)列在_MSGQUEUE內(nèi)部才實(shí)現(xiàn),_MSGQUEUE結(jié)構(gòu)只是將這三種消息隊(duì)列做了一層封裝。其中普通消息的內(nèi)存使用方式為“動(dòng)態(tài)數(shù)組”方式,即通過(guò)malloc方式申請(qǐng)固定大小的連續(xù)內(nèi)存;同步消息和通知消息的內(nèi)存使用方式為“鏈表”方式。

// the MSGQUEUE struct is a internal struct.
// using semaphores to implement message queue.
struct _MSGQUEUE
{
    DWORD dwState;              // message queue states

#ifdef _MGRM_THREADS
    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

#ifdef _MGRM_THREADS
    PSYNCMSG pFirstSyncMsg;     // head of the sync message queue
    PSYNCMSG pLastSyncMsg;      // tail of the sync message queue
#else
    IDLEHANDLER OnIdle;         // Idle handler
#endif

#ifdef _MGRM_THREADS
    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. */
};

miniGUI消息隊(duì)列的接口

消息隊(duì)列作為一種抽象數(shù)據(jù)類型(ADT),是有和它自身相關(guān)的一套接口的,常見(jiàn)的隊(duì)列接口的功能如下:

  • 創(chuàng)建一個(gè)隊(duì)列;
  • 銷毀一個(gè)隊(duì)列;
  • 向隊(duì)列中插入一個(gè)元素;
  • 從隊(duì)列中刪除一個(gè)元素;
  • 返回隊(duì)列頭部的第一個(gè)元素;
  • 判斷隊(duì)列是否為空;
  • 判斷隊(duì)列是否為滿;
  • 返回隊(duì)列的size;
  • 返回隊(duì)列中元素的個(gè)數(shù);
  • ...

創(chuàng)建消息隊(duì)列

首先,看一下miniGUI中創(chuàng)建隊(duì)列的函數(shù)定義,位于src/kernel/message.c文件中,該函數(shù)的原型為:

BOOL mg_InitMsgQueue (PMSGQUEUE pMsgQueue, int iBufferLen);

從其接口中可以看出來(lái),該函數(shù)接收一個(gè)_MSGQUEUE類型的指針(所以在此函數(shù)之前,首先要申請(qǐng)一個(gè)_MSGQUEUE結(jié)構(gòu)體或定義一個(gè)_MSGQUEUE指針),和一個(gè)整數(shù)iBufferLen(用于作為普通消息的消息隊(duì)列的size),返回消息隊(duì)列是否創(chuàng)建成功。下面為該函數(shù)的定義。

BOOL mg_InitMsgQueue (PMSGQUEUE pMsgQueue, int iBufferLen)
{
    memset (pMsgQueue, 0, sizeof(MSGQUEUE));

    pMsgQueue->dwState = QS_EMPTY;

#ifdef _MGRM_THREADS
    pthread_mutex_init (&pMsgQueue->lock, NULL);
    sem_init (&pMsgQueue->wait, 0, 0);
    sem_init (&pMsgQueue->sync_msg, 0, 0);
#endif

    if (iBufferLen <= 0)
        iBufferLen = DEF_MSGQUEUE_LEN;
    
    /* 為消息隊(duì)列申請(qǐng)動(dòng)態(tài)內(nèi)存數(shù)組 */
    pMsgQueue->msg = malloc (sizeof (MSG) * iBufferLen);

    if (!pMsgQueue->msg) {
#ifdef _MGRM_THREADS
        pthread_mutex_destroy (&pMsgQueue->lock);
        sem_destroy (&pMsgQueue->wait);
        sem_destroy (&pMsgQueue->sync_msg);
        return FALSE;
#endif
    }

    pMsgQueue->len = iBufferLen;    //指定普通消息的消息隊(duì)列長(zhǎng)度

    pMsgQueue->FirstTimerSlot = 0;
    pMsgQueue->TimerMask = 0;

    return TRUE;
}

由于只有普通消息的消息隊(duì)列的內(nèi)存使用方式采用“動(dòng)態(tài)數(shù)組”,所以創(chuàng)建或初始化miniGUI的消息隊(duì)列時(shí),只對(duì)普通消息隊(duì)列及其它數(shù)據(jù)成員做初始化,不對(duì)采用鏈表的同步消息和通知消息隊(duì)列初始化。那同步消息和通知消息隊(duì)列的初始化放在哪了呢?
由于同步消息和通知消息采用的是鏈表的內(nèi)存組織方式,每個(gè)節(jié)點(diǎn)的獲取和釋放都是動(dòng)態(tài)的,在初始化miniGUI的消息隊(duì)列時(shí),由于還沒(méi)有分配消息,所以通知消息和同步消息的隊(duì)列指針沒(méi)有指向?qū)嶋H的內(nèi)存位置。通過(guò)mg_InitMsgQueue函數(shù)中的“memset (pMsgQueue, 0, sizeof(MSGQUEUE));”已將這兩類消息的指針設(shè)為NULL。

銷毀消息隊(duì)列

銷毀miniGUI的消息隊(duì)列是由mg_DestroyMsgQueue函數(shù)完成的。該函數(shù)對(duì)通知消息和普通消息的消息隊(duì)列做了銷毀。同步消息由于它的特性,其釋放或銷毀不必放在該函數(shù)內(nèi)進(jìn)行。

void mg_DestroyMsgQueue (PMSGQUEUE pMsgQueue)
{
    PQMSG head;
    PQMSG next;

    head = next = pMsgQueue->pFirstNotifyMsg;
    while (head) {
        next = head->next;

        FreeQMSG (head);
        head = next;
    }

#ifdef _MGRM_THREADS
    pthread_mutex_destroy (&pMsgQueue->lock);
    sem_destroy (&pMsgQueue->wait);
    sem_destroy (&pMsgQueue->sync_msg);
#endif

    mg_remove_timers_by_msg_queue (pMsgQueue);
    
    /* 普通消息隊(duì)列的銷毀 */
    if (pMsgQueue->msg)
        free (pMsgQueue->msg);
    pMsgQueue->msg = NULL;
}

向消息隊(duì)列中插入消息

普通消息的插入

普通消息的插入是通過(guò)PostMessage()函數(shù)和kernel_QueueMessage()函數(shù)完成的。PostMessage()完成對(duì)消息的封裝,kernel_QueueMessage()將普通消息投遞到消息隊(duì)列中。

  1. PostMessage()
  • 根據(jù)窗口句柄獲取消息隊(duì)列句柄;
  • 根據(jù)PostMessage()參數(shù)封裝消息體;
  • 調(diào)用kernel_QueueMessage()向消息隊(duì)列插入消息;
  1. kernel_QueueMessage()
  • 首先是檢查幾種特殊消息:MSG_MOUSEMOVE、MSG_NCMOUSEMOVE、MSG_DT_MOUSEMOVE、MSG_TIMEOUT、MSG_IDLE、MSG_CARETBLINK,如果要插入的消息是這幾種的話,先做特殊處理后,再變相插入到消息隊(duì)列中;
  • 平常消息的插入:
/* Write the data and advance write pointer */
   msg_que->msg [msg_que->writepos] = *msg;
   msg_que->writepos++;
#if 0
//此語(yǔ)句無(wú)效
   if (msg_que->writepos >= msg_que->len) msg_que->writepos = 0;
#else
   msg_que->writepos %= msg_que->len;
#endif

**
通過(guò)這兩個(gè)函數(shù)可以看到,對(duì)于miniGUI消息隊(duì)列中的普通消息,miniGUI是采用動(dòng)態(tài)數(shù)組(malloc分配內(nèi)存)的方式實(shí)現(xiàn)循環(huán)隊(duì)列,讀指針指向循環(huán)隊(duì)列中的頭部第一個(gè)元素,寫指針指向循環(huán)隊(duì)列中尾部的最后一個(gè)元素的下一個(gè)寫位置,并且循環(huán)隊(duì)列采用“空閑一個(gè)位置”的方式避免“判斷隊(duì)列為空或?yàn)闈M時(shí)讀寫指針相同的情況”。**

同步消息的插入

同步消息的插入由SendSyncMessage()函數(shù)完成,該函數(shù)向另一個(gè)線程中的窗口發(fā)送消息,該函數(shù)的處理流程:一先將自己同步消息隊(duì)列中所有的同步消息都處理完,這樣是為了避免死鎖,如果兩個(gè)線程以相差較短的時(shí)間互給對(duì)方發(fā)送同步消息,線程一等待線程二處理消息而阻塞,而線程二也因等待線程一處理消息而阻塞;二是將同步消息插入到另一個(gè)線程的消息隊(duì)列中,這一部分的代碼如下。

    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, 
            "KERNEL>SendSyncMessage: thread is interrupted abnormally!\n");
    }

通知消息的插入

通知消息的插入由SendNotifyMessage()和SendTopNotifyMessage()函數(shù)完成,SendTopNotifyMessage()函數(shù)主要是對(duì)于緊急的消息,亟需被處理,將其插在通知消息的頭部。

  1. SendNotifyMessage()
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);
  1. SendTopNotifyMessage()
    SendTopNotifyMessage()代碼與SendNotifyMessage()類似,此處不再列出。

從消息隊(duì)列中刪除消息

在miniGUI中是從消息隊(duì)列中獲取消息,這部分工作由GetMessage()和PeekMessageEx()函數(shù)完成。GetMessage()只是對(duì)PeekMessageEx()做了一層封裝,內(nèi)部是直接調(diào)用PeekMessageEx()。GetMessage函數(shù)的定義如下:

/**
 * \fn BOOL GetMessage (PMSG pMsg, HWND hMainWnd)
 * \brief Gets a message from the message queue of a main window.
 *
 * This function gets a message from the message queue of the main window 
 * \a hMainWnd, and returns until there is a message in the message queue.
 *
 * \param pMsg Pointer to the result message.
 * \param hMainWnd Handle to the window.
 *
 * \return FALSE on MSG_QUIT have been found or on error, else gets a message.
 *
 * \sa HavePendingMessage, PostQuitMessage, MSG
 *
 * Example:
 *
 * \include getmessage.c
 */
static inline BOOL GUIAPI GetMessage (PMSG pMsg, HWND hWnd)
{
    return PeekMessageEx (pMsg, hWnd, 0, 0, TRUE, PM_REMOVE);
}

GetMessage()只是傳遞兩個(gè)參數(shù),一個(gè)是指向消息緩沖的指針,一個(gè)是要獲取消息的來(lái)源窗體,內(nèi)部調(diào)用PeekMessageEx()又默認(rèn)添加了諸多參數(shù)。PeekMessageEx()函數(shù)的原型為:

//位置:include/window.h - line: 2218
/**
 * \fn BOOL PeekMessageEx (PMSG pMsg, HWND hWnd, \
 *               int iMsgFilterMin, int iMsgFilterMax, \
 *               BOOL bWait, UINT uRemoveMsg)
 * \brief Peeks a message from the message queue of a main window.
 *
 * This functions peek a message from the message queue of the window \a hWnd;
 * if \a bWait is TRUE, it will wait for the message, else return immediatly.
 *
 * \param pMsg Pointer to the result message.
 * \param hWnd The handle to the window.
 * \param iMsgFilterMin The min identifier of the message that should be peeked.
 * \param iMsgFilterMax The max identifier of the message that should be peeked.
 * \param bWait Whether to wait for a message.
 * \param uRemoveMsg Whether remove the message from the message queue.
 *      Should be the following values:
 *      - PM_NOREMOVE\n
 *        Leave it in the message queue.
 *      - PM_REMOVE
 *        Remove it from the message queue.
 *      - PM_NOYIELD
 *        Nouse now.
 *        
 * \return TRUE if there is a message peeked, or FALSE.
 *
 * \sa GetMessage, PeekPostMessage, HavePendingMessage, PostMessage
 */
MG_EXPORT BOOL GUIAPI PeekMessageEx (PMSG pMsg, HWND hWnd, 
                int iMsgFilterMin, int iMsgFilterMax, 
                BOOL bWait, UINT uRemoveMsg);
  1. 參數(shù)iMsgFilterMin和iMsgFilterMax
    iMsgFilterMin和iMsgFilterMax用于限定想要獲取的消息范圍,在PeekMessageEx()中由宏 IS_MSG_WANTED(message)使用,判斷當(dāng)前獲取的消息是否是想要的。該宏的定義 如下:
#define IS_MSG_WANTED(message) \
       ( (iMsgFilterMin <= 0 && iMsgFilterMax <= 0) || \
         (iMsgFilterMin > 0 && iMsgFilterMax >= iMsgFilterMin && \
          message >= iMsgFilterMin && message <= iMsgFilterMax) )

由該宏的定義可知,GetMessage()獲取消息時(shí),獲取到的消息總是想要的。

  1. 參數(shù)bWait
    bWait參數(shù)用于定義當(dāng)消息隊(duì)列中沒(méi)有消息時(shí),是否等待;GetMessage()將該值置為TRUE,表示在消息隊(duì)列中暫時(shí)沒(méi)有消息時(shí),愿意等待。
  2. 參數(shù)uRemoveMsg
    uRemoveMsg參數(shù)用于表征在獲取到一個(gè)消息時(shí),是否將該消息從消息隊(duì)列中刪除。在隊(duì)列的接口中,有“從隊(duì)列中刪除一個(gè)元素”和“返回隊(duì)列頭部的第一個(gè)元素”兩種接口,對(duì)應(yīng)兩種不同的應(yīng)用場(chǎng)景。該參數(shù)正是這兩種接口的另一種解決方式。

由于miniGUI消息隊(duì)列設(shè)計(jì)的特性,除了普通消息、通知消息和同步消息之外,另有幾種消息,比如MSG_QUIT、MSG_TIMER、MSG_PAINT等,這些消息不插入到三種消息隊(duì)列中,而是通過(guò)消息隊(duì)列的標(biāo)志位dwState提現(xiàn)。所以,這些消息不涉及從消息隊(duì)列中刪除消息這一說(shuō),而是如果相應(yīng)的標(biāo)志被置位,pMsg再被重新封裝。
PeekMessageEx()函數(shù)內(nèi)部的其它部分基本就是普通消息、通知消息和同步消息從消息隊(duì)列中的獲取。

  1. 同步消息的獲取
   if (pMsgQueue->dwState & QS_SYNCMSG) {
       /* 檢查隊(duì)列是否為空 */
       if (pMsgQueue->pFirstSyncMsg) {
           *pMsg = pMsgQueue->pFirstSyncMsg->Msg;
           SET_PADD (pMsgQueue->pFirstSyncMsg);
           if (IS_MSG_WANTED(pMsg->message)) {
             if (uRemoveMsg == PM_REMOVE) {
               /* 從消息隊(duì)列中刪除該消息 */
                 pMsgQueue->pFirstSyncMsg = pMsgQueue->pFirstSyncMsg->pNext;
             }

             UNLOCK_MSGQ (pMsgQueue);
             return TRUE;
           }
       }
       else    /* 隊(duì)列為空則去掉QS_SYNCMSG的置位 */
           pMsgQueue->dwState &= ~QS_SYNCMSG;
   }
  1. 通知消息的獲取
if (pMsgQueue->dwState & QS_NOTIFYMSG) {
        /* 檢查隊(duì)列是否為空 */
        if (pMsgQueue->pFirstNotifyMsg) {
            phead = pMsgQueue->pFirstNotifyMsg;
            *pMsg = phead->Msg;
            SET_PADD (NULL);

            if (IS_MSG_WANTED(pMsg->message)) {
              if (uRemoveMsg == PM_REMOVE) {
                /* 從消息隊(duì)列中刪除該消息 */
                  pMsgQueue->pFirstNotifyMsg = phead->next;
                  FreeQMSG (phead);
              }

              UNLOCK_MSGQ (pMsgQueue);
              return TRUE;
            }
        }
        else    /* 隊(duì)列為空則去掉QS_NOTIFYMSG的置位 */
            pMsgQueue->dwState &= ~QS_NOTIFYMSG;
    }
  1. 普通消息的獲取
if (pMsgQueue->dwState & QS_POSTMSG) {
        /* 檢查隊(duì)列是否為空 */
        if (pMsgQueue->readpos != pMsgQueue->writepos) {
            *pMsg = pMsgQueue->msg[pMsgQueue->readpos];
            SET_PADD (NULL);
            if (IS_MSG_WANTED(pMsg->message)) {
                CheckCapturedMouseMessage (pMsg);
                /* 從消息隊(duì)列中刪除該消息 */
                if (uRemoveMsg == PM_REMOVE) {
                    pMsgQueue->readpos++;
                    /* #if 0 中對(duì)循環(huán)隊(duì)列的兩種處理方式都可行 */
#if 0
                    if (pMsgQueue->readpos >= pMsgQueue->len)
                        pMsgQueue->readpos = 0;
#else
                    pMsgQueue->readpos %= pMsgQueue->len;
#endif
                }

                UNLOCK_MSGQ (pMsgQueue);
                return TRUE;
            }
        }
        else    /* 隊(duì)列為空則去掉QS_POSTMSG的置位 */
            pMsgQueue->dwState &= ~QS_POSTMSG;
    }

Notes

隊(duì)列的實(shí)現(xiàn)有多種方式,按內(nèi)存的申請(qǐng)方式包括靜態(tài)數(shù)組、動(dòng)態(tài)數(shù)組、鏈表。通過(guò)靜態(tài)數(shù)組和動(dòng)態(tài)數(shù)組實(shí)現(xiàn)的隊(duì)列,通常為了避免“判斷隊(duì)列為空或?yàn)闈M的隊(duì)尾、對(duì)頭指針指向相同”的問(wèn)題,又將隊(duì)列實(shí)現(xiàn)為循環(huán)隊(duì)列。以下為各種隊(duì)列的實(shí)現(xiàn)方式:

  1. 來(lái)源:CSDN
    作者:乞力馬扎羅的雪雪
    文章:C語(yǔ)言實(shí)現(xiàn)使用靜態(tài)數(shù)組實(shí)現(xiàn)循環(huán)隊(duì)列
  2. 來(lái)源:CSDN
    作者:乞力馬扎羅的雪雪
    文章:C語(yǔ)言實(shí)現(xiàn)使用動(dòng)態(tài)數(shù)組實(shí)現(xiàn)循環(huán)隊(duì)列
  3. 來(lái)源:網(wǎng)易博客
    作者:Dr
    文章:用鏈表實(shí)現(xiàn)隊(duì)列

Others

  • miniGUI私有塊數(shù)據(jù)堆(Private Block Data Heap)
    miniGUI為了提高性能,不過(guò)于頻繁地調(diào)用內(nèi)存管理函數(shù)(malloc和free),自身維護(hù)一些用于分配數(shù)據(jù)塊的塊數(shù)據(jù)堆,這些數(shù)據(jù)塊有一個(gè)特性就是大小固定,比如一個(gè)區(qū)域中的剪切矩形。塊數(shù)據(jù)堆的定義如下。
typedef struct _BLOCKHEAP
{
#ifdef _MGRM_THREADS
   pthread_mutex_t lock;
#endif
   /**
    * Size of one block element.
    */
   size_t          bd_size;
   /**
    * Size of the heap in blocks.
    */
   size_t          heap_size;
   /**
    * The first free element in the heap.
    * 用于跟蹤分配的堆中第一個(gè)可用的塊--空塊
    */
   int             free;
   /**
    * Pointer to the pre-allocated heap.
    * 通過(guò)malloc得到的內(nèi)存模擬堆的起始地址
    */
   void*           heap;
} BLOCKHEAP;
typedef BLOCKHEAP* PBLOCKHEAP;

通知消息的分配方式就是從塊數(shù)據(jù)堆中分配,miniGUI將QMSGHeap定義為全局靜態(tài)變量,該通知消息(有待考證)堆的初始化和銷毀通過(guò)mg_InitFreeQMSGList和mg_DestroyFreeQMSGList完成。

static BLOCKHEAP QMSGHeap;
//Interface
BOOL mg_InitFreeQMSGList (void);
void mg_DestroyFreeQMSGList (void);
inline static PQMSG QMSGAlloc (void);
inline static void FreeQMSG (PQMSG pqmsg);

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容